ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

JSP+Servlet+JDBC+MySQL学生管理系统:从底层原理到实战排查

2026/9/8 3:13:58 拓冰建站 浏览量
JSP+Servlet+JDBC+MySQL学生管理系统:从底层原理到实战排查 简介基于 JSPServletJDBCMySQL 的学生管理系统面向 Java Web 方向的初、中级学习者和需要完成毕业设计、期末设计的高校学生定位为一套可直接运行、便于二次开发的典型 Web 管理项目。压缩包共 341 个文件、约 6.99MB以 84 个 Java 源文件、84 个 Class 文件、36 个 JSP 页面、61 个 JS 脚本和 28 个 CSS 样式为主体同时包含 SQL 初始化脚本、项目配置文件和少量图片资源整体目录结构规范导入 IDE 后即可进行数据库还原与项目部署。工程按分层思想组织代码DAO 层基于 JDBC 完成用户、教师、学生、课程、成绩等实体的数据库操作Service/JavaBean 负责业务逻辑Servlet 与 JSP 实现前台控制和页面展示包含登录校验、信息增删改查等常见功能作者上传前已在本机测试通过代码可顺利运行。目前已有 3320 人学习或下载对想掌握 JSPServlet 分层开发、SQL 联表查询或者准备课程设计、毕业论文的学生来说是一份不错的参考资料。 每个学Java Web的人大概率都会在某个阶段碰上一个“基于jspservletjdbcMySQL的学生管理系统”。这个项目看起来朴素甚至被很多人说“技术栈太老”但这么多年过去它依然是课程设计、毕业设计和自学练手最常见的题目没有之一。原因很简单它不是最潮的架构却把Web开发最底层的请求-响应模型、会话管理、数据库交互和页面渲染串成了一条完整链路是理解后续所有框架的基石。这篇文章就围绕这个经典项目展开从整体设计、数据库与JDBC连接、Servlet控制层、JSP页面交互到分页、上传、Excel导出等常见扩展功能再到我最想分享的排查技巧。不管你是正在做课设的学生还是想补Java Web基础的自学者这里面的东西都值得花十分钟看完。1. 项目整体设计思路拆解1.1 为什么jspservletjdbcMySQL这套组合依然值得写先回答一个很多人纠结的问题现在Spring Boot都卷到不行了为什么还要写jspservletjdbc我的看法是这个技术栈最大的价值在于“没有魔法”。使用Spring Boot时一个RequestMapping注解进去请求怎么到Controller、事务怎么开启、连接池怎么管理很多新手是说不清的。而在原生的Servlet工程里用户点了按钮表单数据通过HTTP协议发到TomcatTomcat找到web.xml里注册的Servlet映射doPost()方法里一行行拿到参数、调用JDBC、拼装结果、request.getRequestDispatcher().forward()转发回JSP渲染成HTML——每一步都是透明的没有任何框架掩盖细节。把这些原理吃透了后面学Spring MVC基本是降维打击。从另一个角度说这个项目的业务边界也非常适合练手。学生管理涉及登录认证、增删改查、条件搜索、分页、批量删除、文件上传头像、数据导出Excel甚至视频播放个人信息页放个MP4几乎覆盖了Java Web日常开发的高频场景每个功能既能独立实现又能组合成完整系统非常适合作为综合练习。1.2 经典三层结构在项目里怎么落地参考常见的课程设计要求我习惯把这个项目按三层结构拆开表示层JSP页面负责展示数据和收集用户输入。jsp页面放在webapp目录下通过${}EL表达式和JSTL标签库简化Java代码的嵌入避免满屏%%脚本片段。业务控制层Servlet接收请求、校验参数、调用Service或直接调用DAO课设规模不必强行分层、决定跳转路径。一个Servlet类最好只处理一类功能模块的职责比如StudentServlet负责学生相关的请求通过action参数区分增删改查操作。数据访问层DAO/JDBC封装所有SQL操作对外提供findById、findAll、insert、update、delete等方法。这一层要负责连接的获取与关闭、PreparedStatement的创建和结果集的转换。这样做的好处非常明显JSP里不会出现一大堆Java操作数据库的代码Servlet不会变成几千行的巨无霸日后想更换数据库或改造业务逻辑时只需要动DAO层而不必触碰页面代码。虽然项目规模小但规范一旦建立扩展起来会省非常多的事。2. 数据库设计与JDBC连接层实现2.1 MySQL建表与字段设计要点学生管理系统的核心表基本逃不出几个学生表、用户表管理员/教师登录、可能还有班级表或专业表。以学生表为例字段建议这样设计CREATE TABLE student ( id INT(11) NOT NULL AUTO_INCREMENT, stu_no VARCHAR(20) NOT NULL COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender CHAR(1) DEFAULT 男 COMMENT 性别, age INT(3) DEFAULT NULL, major VARCHAR(100) DEFAULT NULL COMMENT 专业, phone VARCHAR(20) DEFAULT NULL, email VARCHAR(100) DEFAULT NULL, avatar VARCHAR(255) DEFAULT NULL COMMENT 头像路径, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_stu_no (stu_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;几个设计上的细节值得注意。第一主键用自增id学号单独加唯一索引避免用学号当主键导致后续修改麻烦。第二create_time用DEFAULT CURRENT_TIMESTAMP插入数据时不用手动填时间。第三字符集务必用utf8mb4而不是utf8否则存emoji或者部分生僻字会出现乱码。第四avatar字段存的是文件保存后的相对路径比如uploads/avatar_001.jpg而不是二进制数据这点后面讲文件上传时还会再提。2.2 JDBC连接工具类的标准写法连接管理是整个项目最基础也最容易出错的部分。我先给出一个比较稳妥的工具类写法然后用它说明几个关键点。package com.demo.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/student_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (stmt ! null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }连接URL里面参数非常多每一样都有讲究。useUnicodetruecharacterEncodingutf8保证中文和参数正常传输useSSLfalse关掉SSL警告serverTimezoneAsia/Shanghai是为了解决MySQL 8.x和驱动之间的时区差异allowPublicKeyRetrievaltrue是因为MySQL 8.0默认的caching_sha2_password认证方式在本地非SSL连接时会报Public Key Retrieval is not allowed加上它才能正常连上。静态代码块里的Class.forName(com.mysql.cj.jdbc.Driver)是传统做法虽然JDBC 4.0之后可以通过SPI机制自动加载驱动但保险起见还是显式加载。close方法必须分别处理ResultSet、Statement、Connection顺序不能乱先结果集后语句最后连接。如果业务代码中忘关连接本地开发时可能感觉不到问题跑上一段时间就会出现Too many connections。2.3 要不要用连接池说实话课程设计这种并发量极低的应用直接DriverManager.getConnection()完全够用。但如果你想在项目里体现一点工程意识或者老师要求“数据库连接要优化”建议引入Druid连接池也就多几十行配置。Druid的配置其实就三块druid.properties配置文件、初始化连接池的工具类、在DAO中从池子拿连接。配置文件放在src目录下内容大致是driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/student_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai usernameroot password123456 initialSize5 maxActive10 maxWait3000工具类使用Druid的DruidDataSourceFactory来加载配置private static DataSource dataSource; static { try (InputStream in DBUtil.class.getClassLoader().getResourceAsStream(druid.properties)) { dataSource DruidDataSourceFactory.createDataSource(in); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); }引入连接池后DAO层的代码不用改太多不用再写Class.forName连接从池子里借、用完后close()其实是还回池子里。这里有个容易踩的坑Druid连接池默认对空连接检测有testWhileIdle机制如果MySQL服务端wait_timeout过短导致连接失效报错往往诡异比如各种奇怪的通信异常排查时可以先把validationQuerySELECT 1加上。3. Servlet控制层与JSP页面实现要点3.1 用web.xml写Servlet还是注解“使用web.xml方式编写servlet的完整示例”是很多人搜索过的需求原因在于很多教材和课程设计要求用web.xml配置映射。虽然WebServlet注解从Servlet 3.0开始支持且非常方便但web.xml的方式能更直观地展示Servlet的注册过程也是一些老旧系统的标准做法。web.xml放到WEB-INF目录下核心片段如下servlet servlet-nameStudentServlet/servlet-name servlet-classcom.demo.servlet.StudentServlet/servlet-class load-on-startup1/load-on-startup /servlet servlet-mapping servlet-nameStudentServlet/servlet-name url-pattern/student/url-pattern /servlet-mappingload-on-startup填整数表示容器启动时创建的优先级数字越小越先加载。这个配置常用来预加载全局初始化的Servlet。url-pattern是浏览器访问的路径映射请求到/student的URL都会交给StudentServlet处理。在实际项目中一个项目通常有多个Servlet所以我常用的做法是所有*.do结尾的请求统一走某个入口Servlet内部再分发给不同方法类似一个简单的前端控制器。但这属于设计偏好了课设里按模块各建一个Servlet也完全没问题。3.2 doGet与doPost的取舍和乱码处理Servlet最核心的两个方法是doGet和doPost。GET请求参数会拼在URL后面适合查询和跳转POST请求参数在请求体里适合提交数据、上传文件这类操作。我习惯在Servlet里重写service方法统一处理编码或者在doGet里调用doPost这样无论表单用什么方式提交都不会漏掉。中文乱码是新手最常见的拦路虎而且原因不止一个。Servlet接收参数时要判断请求方式POST请求通过request.setCharacterEncoding(UTF-8)设置请求体的编码GET请求参数在URL中Tomcat默认按ISO-8859-1解码所以还要在配置文件里加上连接器编码Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 /输出响应时要设置response.setContentType(text/html;charsetUTF-8)JSP页面顶部还要写上% page contentTypetext/html;charsetUTF-8 languagejava %。这三处缺一不可。MySQL连接URL里的characterEncodingutf8也已经处理了数据库这层四层编码对齐了中文才会真正稳定不乱码。3.3 请求转发与重定向的选择Servlet处理完业务后要跳转页面有两条路request.getRequestDispatcher(list.jsp).forward(request, response)和response.sendRedirect(list.jsp)。这两者的区别非常基本但极其重要。转发发生在服务器内部浏览器地址栏不变request对象里保存的属性比如查询出来的学生列表在目标页面还能用重定向则是一次新的请求浏览器地址栏会变化之前request里的属性全部丢失。所以需要携带数据渲染页面时必须用转发。而新增数据、删除数据这类操作为了防止用户刷新页面导致重复提交应该redirect到另一个查询的URL。我见过不少项目的删除操作是用转发方式跳转回列表页结果用户按一下F5同一个删除请求被重复执行。如果数据库没有做关联约束数据可能被重复删除或报错。这一点在课设答辩时经常被老师问搞清楚原理回答起来会很从容。3.4 JSP页面的EL表达式和JSTLJSP页面里最忌讳的就是满屏% %脚本片段既难看又难维护。建议引入EL表达式和JSTL标签库让页面专注展示。比如学生列表页面用c:forEach循环渲染表格% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table trth学号/thth姓名/thth专业/thth操作/th/tr c:forEach items${studentList} varstu tr td${stu.stuNo}/td td${stu.name}/td td${stu.major}/td td a hrefstudent?actioneditid${stu.id}编辑/a a hrefstudent?actiondeleteid${stu.id} onclickreturn confirm(确定删除吗)删除/a /td /tr /c:forEach /table条件判断用c:if比如性别显示c:if test${stu.gender 男}男/c:if。分页导航可以用c:forEach配合begin和end生成页码。这些标签语法简单但能显著减少Java代码在页面中的比重也是老师比较喜欢的代码风格。注意JSTL需要引入两个jar包jstl.jar和standard.jar版本选择上建议用1.2版本Java 8以上环境都能兼容。如果页面报错说找不到taglib描述符检查一下WEB-INF/lib下这两个jar是否存在。4. 核心功能模块的完整实现路径4.1 登录认证与会话管理登录模块看上去简单但牵扯到Session生命周期、密码安全、页面拦截三个知识点。常见的实现逻辑是用户提交用户名和密码Servlet从数据库查询比对成功后把用户信息放入Session中User user userDao.findByUsernameAndPwd(username, password); if (user ! null) { request.getSession().setAttribute(loginUser, user); response.sendRedirect(student?actionlist); } else { request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); }Session的默认超时时间是30分钟可以在web.xml中配置session-config session-timeout30/session-timeout /session-config页面拦截有两种实现方式。最简单的是写一个LoginFilter实现javax.servlet.Filter接口在doFilter里检查Session中是否存在登录标记没有则跳回登录页。另一种是每个页面用% if(session.getAttribute(loginUser)null) response.sendRedirect(login.jsp); %做判断代码冗余且容易遗漏。Filter更优雅也是标准做法。密码存储方面课程设计里明文存库的很多但我建议至少用MD5加盐处理一下。DigestUtils.md5DigestAsHex((password salt).getBytes())几行代码就能搞定在数据库里加个salt字段注册时生成随机盐登录时再拼起来加密比对。不加密的后果就是数据库一旦泄露所有密码裸奔这个习惯从课设开始养成没有坏处。4.2 学生信息的增删改查与分页学生模块的CRUD相对直接重点在分页查询和条件查询。分页的核心SQL是LIMIT ? , ?第一页返回起始行第二页返回每页大小。Service层要计算出总页数并把当前页、总页数、每页大小、查询结果集一起封装成PageBean对象传给JSP。public class PageBeanT { private int pageNum; // 当前页码 private int pageSize; // 每页条数 private int totalCount; // 总记录数 private int totalPages; // 总页数 private ListT list; // 当前页数据 }计算逻辑是int totalPages totalCount % pageSize 0 ? totalCount / pageSize : totalCount / pageSize 1;条件查询时要动态拼接SQL这里务必使用PreparedStatement占位符千万不能字符串拼SQL否则SQL注入的风险极大。例如String sql SELECT * FROM student WHERE 11; ListObject paramList new ArrayList(); if (name ! null !.equals(name.trim())) { sql AND name LIKE ?; paramList.add(% name.trim() %); } if (major ! null !.equals(major.trim())) { sql AND major ?; paramList.add(major.trim()); } if (pageNum 0) { sql LIMIT ?, ?; paramList.add((pageNum - 1) * pageSize); paramList.add(pageSize); } pstmt conn.prepareStatement(sql); for (int i 0; i paramList.size(); i) { pstmt.setObject(i 1, paramList.get(i)); }WHERE 11是纯为了方便拼接条件的技巧条件恒真后面的AND可以直接拼不用判断是不是第一段。这种做法在生成动态SQL时很常见性能影响可以忽略但代码简洁不少。修改和删除操作按id操作前端传id参数DAO层用PreparedStatement绑定参数流程上没有太多玄机主要注意前端表单的action地址和方法要正确以及删除时用confirm弹窗确认避免误删。4.3 图片上传与MP4视频播放课设中“jsp个人信息展示页面”“jsp实现mp4视频播放”这两个需求经常被一起提出。实际上照片和视频的展示模式是相同的文件不要存数据库而是存到服务器磁盘的某个目录数据库只存路径页面通过相对路径去访问。文件上传如果只用原生Servlet表单要设enctypemultipart/form-data然后借助Apache Commons FileUpload组件解析。引入commons-fileupload-1.4.jar和commons-io-2.11.0.jar两个依赖后Servlet里可以用ServletFileUpload解析request拿到FileItem列表String uploadPath getServletContext().getRealPath(/uploads); File uploadDir new File(uploadPath); if (!uploadDir.exists()) uploadDir.mkdirs(); DiskFileItemFactory factory new DiskFileItemFactory(); ServletFileUpload upload new ServletFileUpload(factory); ListFileItem items upload.parseRequest(request); for (FileItem item : items) { if (!item.isFormField()) { String fileName UUID.randomUUID().toString().replace(-, ) _ new File(item.getName()).getName(); File file new File(uploadPath, fileName); item.write(file); // 将 fileName 存到数据库的 avatar 字段 } }文件名用UUID重命名是为了防止不同用户上传同名文件互相覆盖也是基本的规范做法。getRealPath(/uploads)会把文件保存到项目部署后的webapp目录下这样浏览器直接访问/uploads/文件名就能读到。视频播放更简单JSP里放一个video标签src指向uploads/xxx.mp4即可后端不需要额外处理。需要注意两点一是webapps目录下文件会被容器直接访问但要确认uploads目录没有放在WEB-INF内部否则外部访问不到二是Tomcat默认对http的POST大小有限制maxPostSize默认2MB如果上传的视频较大需要去server.xml里调大maxPostSize-1或者单独限制上传文件大小。4.4 数据导出为Excel“jsp实现数据导出为excel”也是常见扩展点。最简单的实现方式是POI生成.xls文件并响应给浏览器浏览器端会触发下载。HSSFWorkbook workbook new HSSFWorkbook(); HSSFSheet sheet workbook.createSheet(学生信息); HSSFRow headerRow sheet.createRow(0); headerRow.createCell(0).setCellValue(学号); headerRow.createCell(1).setCellValue(姓名); headerRow.createCell(2).setCellValue(专业); int rowNum 1; for (Student stu : list) { HSSFRow row sheet.createRow(rowNum); row.createCell(0).setCellValue(stu.getStuNo()); row.createCell(1).setCellValue(stu.getName()); row.createCell(2).setCellValue(stu.getMajor()); } response.setContentType(application/vnd.ms-excel); response.setHeader(Content-Disposition, attachment; filenamestudents.xls); workbook.write(response.getOutputStream()); workbook.close();需要引入poi相关jar包建议用4.1.2版同时需要poi-ooxml和poi核心包。导出的Excel按钮放列表页点击后请求一个单独的ExportServlet不走转发也不重定向到其他页面因为浏览器要接收输出流下载文件。如果文件名里有中文Content-Disposition需要对中文做URL编码否则下载时文件名乱码filename*UTF-8 URLEncoder.encode(name, UTF-8)。这个坑比较隐蔽很多人导出的内容没问题但文件名是乱码多半就出在这里。5. 常见问题与排查技巧实录5.1 JDBC连接报错的常见场景项目中遇到最多的报错整理成一个速查表下次碰到可以直接对照排查。报错信息原因解决办法No suitable driver found for jdbc:mysql://...MySQL驱动jar没有导入或URL格式错误检查WEB-INF/lib下驱动jar检查URL前缀是否为jdbc:mysql://8.x驱动类为com.mysql.cj.jdbc.DriverPublic Key Retrieval is not allowedMySQL 8.0默认认证插件是caching_sha2_password连接URL拼接allowPublicKeyRetrievaltrueAccess denied for user rootlocalhost用户名密码错误或远程连接权限问题检查账号密码本地连接确认host为localhostToo many connections连接数溢出通常是连接未关闭检查DAO中close是否都执行引入连接池限制最大连接数name JDBC is not bound in this context代码里用JNDI方式查找DataSource但环境中未配置改用本地Druid连接池或DriverManager方式或者检查context.xml数据源配置其中name JDBC is not bound in this context比较特殊它是JNDI数据源相关错误。用Tomcat配置JNDI数据源时代码要从InitialContext查找java:comp/env/jdbc/studentDB如果context.xml没配置或者配置名称对不上就会报这个错。课设场景我建议直接用Druid连接池少踩一层配置的坑。5.2 页面改动不生效、乱码和日期显示问题JSP改了不生效九成原因是浏览器缓存或Tomcat没有重新编译。开发阶段可以在web.xml中给JSP的Servlet配置修改检查参数developmenttrue。如果用的是IDEA的Artifact部署模式改了JSP有时需要重新构建或者重启Tomcat这是因为IDE没有同步更新到target目录。遇到这种问题先mvn clean再重新部署或者直接Rebuild项目。乱码问题前面提过四个层面的编码这里再给一个快速定位的方法先看页面显示乱码还是数据中文乱码。页面乱码通常是JSP的pageEncoding没设数据显示成?通常是数据库字符集不对检查建表语句的DEFAULT CHARSETutf8mb4参数传过去乱码则是请求编码问题检查Servlet里的setCharacterEncoding和Tomcat的URIEncoding。日期类型的处理也容易翻车。数据库DATETIME类型在JDBC中对应java.sql.Timestamp从ResultSet取值后用SimpleDateFormat格式化输出Date createTime rs.getTimestamp(create_time); String createTimeStr new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(createTime);如果你在JavaBean里用的是java.util.Date直接用rs.getTimestamp赋值是可以的但不要用rs.getDate它会丢掉时分秒。另一方表单里如果涉及日期字符串转数据库日期用ps.setTimestamp(1, Timestamp.valueOf(dateStr 00:00:00))注意格式必须严格匹配否则会抛IllegalArgumentException。5.3 我踩过的几个典型坑写这个项目时我印象最深的是文件上传路径问题。用getRealPath(/uploads)拿到的路径在IDEA部署时指向的是target/项目名/uploads开发环境下文件是能访问的但每次mvn clean一执行整个target目录被清空上传的文件也没了。如果是一个长期运行的系统更稳妥的做法是把上传目录配置成外部绝对路径比如/home/user/uploads或Windows下的D:/uploads然后写一个虚拟目录映射或者用自定义Servlet把文件流读出来返回。课程设计的话用getRealPath就能跑通但答辩时如果能主动说清楚这个局限并提出外部存储的改进方案会是非常好的加分项。另一个坑是MySQL 8.0驱动版本不一致导致时区异常。有次项目用的驱动是5.1.47连MySQL 8.0数据库报Unknown system variable query_cache_size搜了很久才明白是驱动太老。解决就是统一用8.0.x的mysql-connector-java并加上serverTimezoneAsia/Shanghai参数。再说一个关于删除功能的小设计删除操作最好用POST请求而不是GET链接。很多人写着a hrefstudent?actiondeleteid1删除/a这样的链接会被搜索引擎爬虫、浏览器预加载插件触发也可能被用户误点。更合理的方式是用一个表单包裹按钮用typesubmit提交方式为POST。课设虽然不会有人恶意刷但养成了GET只做查询的好习惯以后工作会受益。最后分享一个小技巧调试SQL时在DAO层把即将执行的PreparedStatement的SQL语句和参数打印到控制台。最简单的方式就是写一个log变量拼接占位符替换后的完整SQL或者临时用com.mysql.cj.jdbc.interceptors.SQLExceptionInterceptor这类插件但项目里直接打日志已经很直观了。打印出的SQL拷贝到Navicat里手动执行一遍很多问题瞬间就能定位到底是SQL写错还是代码传参有误。踩过几次坑之后你会发现这个项目的所有问题几乎都集中在编码、驱动、路径、连接管理这四个方向。把这四个问题控住了整个系统的稳定性会大大提高答辩时也能更有底气地和老师聊设计思路而不是只说“网上找的模板能跑”。本文还有配套的精品资源点击获取