JavaWeb学生管理系统毕设实战:Servlet+JSP+MySQL完整案例与避坑指南

发布时间:2026/10/7 14:39:43
JavaWeb学生管理系统毕设实战:Servlet+JSP+MySQL完整案例与避坑指南 简介这是一套面向计算机相关专业毕设学生与Java项目实战练习者的学生管理系统完整源码包采用Servlet、JSP、JDBC、DbUtils搭建后台配合EasyUI、jQuery、Ajax实现界面交互数据层使用MySQL存储。系统按学生、教师、管理员三种角色划分权限学生可管理个人信息、查看同学通讯录、查询成绩与修改密码教师可登记、统计并导出成绩维护通讯录管理员负责基础信息、学生与教师信息、考试及后台管理功能覆盖较完整。资源包共15个文件约13.49MB包含项目源码压缩包、数据库脚本、项目文档PDF与Markdown、运行截图及软件工具说明等便于快速部署与二次开发。目前已有18530人学习下载适合需要完整毕设方案、可运行代码与配套文档参考的读者也可作为JavaWeb入门到综合实战的练习素材。1. 从一份“能跑起来”的 JavaWeb 学生管理系统说起很多同学做毕设时选题定了“基于 JavaWeb 实现的学生管理系统”真正动手才发现卡点不在写代码而在“怎么把一堆 Servlet、JSP、MySQL 拼成一个能演示、能答辩、能交差的项目”。这个标题背后其实是一套非常典型的 JavaWeb 技术栈组合前端用 JSP 或 HTMLAJAX后端用 Servlet 或 Spring MVC数据库用 MySQL构建工具用 Maven服务器用 Tomcat。它解决的核心问题是把学生信息、课程信息、成绩信息、用户权限这几类数据通过浏览器界面完成增删改查和登录鉴权。适合谁看如果你是计算机专业本科毕设选手或者正在做 Java 课程设计需要一套结构清晰、能本地跑通、方便二次修改的完整案例那这篇笔记就是按这个目标写的。我不会只讲概念而是把项目结构、数据库建表、后端接口、前端页面、IDEA 运行配置、常见翻车点都拆开讲。你照着做至少能得到一个能登录、能管理学生、能查成绩、能改密码的完整系统。至于“源码”我会给出关键代码和目录组织方式你按这个骨架填充业务即可不必依赖某一份不存在的压缩包。2. 技术选型与项目骨架为什么用 ServletJSP 而不是一上来就 Spring Boot2.1 毕设场景下的技术栈取舍很多同学一搜“javaweb项目完整案例mysql”看到的大部分是 Spring Boot Vue 的前后端分离方案。但毕设答辩时老师往往更关注你对 JavaWeb 基础的理解请求怎么进来、Session 怎么管、JDBC 怎么连、MVC 怎么分层。如果直接用 Spring Boot 自动配置反而容易被问“你这个拦截器底层是什么”“事务怎么控制的”时答不上来。我一般会建议如果学校要求“JavaWeb”方向优先用 Servlet JSP JDBC 这套原生组合。原因有三点。第一代码透明每一层你都能说清楚第二依赖少不需要折腾 Node.js、npm、跨域代理第三IDEA 运行 javaweb 项目配置相对简单Tomcat 一挂就能跑。代价是前端页面偏“传统”但毕设够用。技术栈定下来后端Java Servlet 3.1、JSP 2.3、JDBC数据库MySQL 5.7 或 8.0连接池Druid 或 HikariCP二选一推荐 Druid监控方便前端JSP JSTL Bootstrap 5直接 CDN 引入不折腾构建服务器Tomcat 8.5 或 9.0构建Maven打 war 包开发工具IntelliJ IDEA提示不要用 Tomcat 10因为 Servlet 包名从 javax.servlet 变成了 jakarta.servlet网上大部分 JavaWeb 教程代码会直接报错。毕设求稳选 Tomcat 9。2.2 Maven 项目目录结构与依赖配置先建一个 Maven Web 项目目录结构如下。这个结构是后面所有代码的容器建议一开始就建对不然后面改包名很痛苦。student-management/ ├── pom.xml ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/sms/ │ │ │ ├── dao/ # 数据库访问 │ │ │ ├── entity/ # 实体类 │ │ │ ├── servlet/ # 控制器 │ │ │ ├── filter/ # 登录过滤器 │ │ │ └── util/ # JDBC 工具类 │ │ ├── resources/ │ │ │ └── db.properties # 数据库配置 │ │ └── webapp/ │ │ ├── WEB-INF/ │ │ │ └── web.xml │ │ ├── css/ │ │ ├── js/ │ │ └── *.jsppom.xml 里核心依赖如下注意 scope 和版本。dependencies !-- Servlet API由 Tomcat 提供打包时不带入 -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency !-- JSP API同样 provided -- dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency !-- JSTL 标签库运行时要带上 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency !-- MySQL 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency !-- Druid 连接池 -- dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.20/version /dependency /dependencies逻辑说明provided表示这些包由 Tomcat 运行时提供如果你打成 war 包还带进去可能和 Tomcat 自带版本冲突出现ClassNotFoundException或NoSuchMethodError。MySQL 驱动版本要和你的数据库版本匹配8.0 的驱动连 5.7 也能用但 URL 要加时区参数。参数说明mysql-connector-java从 8.0 开始推荐用com.mysql.cj.jdbc.DriverURL 写成jdbc:mysql://localhost:3306/sms?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8。少了serverTimezone会报时区错误这是新手最常见的翻车点之一。3. 数据库设计与后端接口从建表到 Servlet 返回 JSON3.1 四张核心表与建表 SQL学生管理系统的数据模型不复杂但字段设计要经得起答辩追问。我一般会建四张表用户表、学生表、课程表、成绩表。用户表负责登录和角色学生表存学籍信息课程表存课程成绩表做关联。CREATE DATABASE sms DEFAULT CHARACTER SET utf8mb4; USE sms; -- 用户表管理员和普通用户 CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role VARCHAR(20) DEFAULT admin, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 学生表 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, sno VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL, gender VARCHAR(4) DEFAULT 男, age INT, major VARCHAR(50), phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 课程表 CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, cno VARCHAR(20) NOT NULL UNIQUE COMMENT 课程号, cname VARCHAR(50) NOT NULL, credit DECIMAL(3,1) DEFAULT 2.0, teacher VARCHAR(50) ); -- 成绩表学生和课程多对多 CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,1), exam_time DATE, FOREIGN KEY (student_id) REFERENCES student(id), FOREIGN KEY (course_id) REFERENCES course(id) );逻辑说明sno和cno都加了唯一索引防止重复录入。成绩表用外键关联学生和课程这样查询某个学生的所有成绩时可以直接 JOIN。utf8mb4是为了支持中文和特殊字符别用utf8否则某些生僻字会乱码。参数说明DECIMAL(5,1)表示成绩最多 5 位、保留 1 位小数比如 100.0。create_time默认当前时间方便排序。如果学校要求“学生选课管理系统”你只需要在成绩表基础上加一个选课状态字段即可结构不用大改。3.2 JDBC 工具类与 Druid 连接池配置不要在每个 Servlet 里写DriverManager.getConnection那样连接数一多就崩。用 Druid 连接池配合db.properties配置文件。# src/main/resources/db.properties driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/sms?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 usernameroot password123456 initialSize5 maxActive20 maxWait3000// com.example.sms.util.JdbcUtil public class JdbcUtil { private static DataSource dataSource; static { try { Properties props new Properties(); // 从类路径加载配置文件 props.load(JdbcUtil.class.getClassLoader() .getResourceAsStream(db.properties)); dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(数据库连接池初始化失败); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } // 释放资源注意关闭顺序 public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException ignored) {} try { if (stmt ! null) stmt.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }逻辑说明静态代码块只在类加载时执行一次连接池只初始化一次。DruidDataSourceFactory会根据 properties 里的 key 自动映射参数。close方法里连接是“归还”给连接池不是真正关闭所以顺序不能乱。参数说明initialSize是初始连接数毕设演示 5 够用maxActive是最大连接数20 足够maxWait是获取连接的超时时间单位毫秒超过就抛异常。如果演示时页面卡住不动先看这里是不是设太小。3.3 学生列表 Servlet从查询到 JSON 返回前端用 AJAX 请求数据时后端返回 JSON 最方便。这里不引入 Gson/Jackson 也行但为了代码清晰建议加一个轻量 JSON 库。下面用原生拼接演示理解原理后你再换库。// com.example.sms.servlet.StudentListServlet WebServlet(/student/list) public class StudentListServlet extends HttpServlet { private StudentDao studentDao new StudentDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType(application/json;charsetutf-8); ListStudent list studentDao.findAll(); // 手动拼接 JSON实际项目建议用 Jackson StringBuilder json new StringBuilder([); for (int i 0; i list.size(); i) { Student s list.get(i); json.append(String.format( {\id\:%d,\sno\:\%s\,\name\:\%s\,\gender\:\%s\,\major\:\%s\}, s.getId(), s.getSno(), s.getName(), s.getGender(), s.getMajor())); if (i list.size() - 1) json.append(,); } json.append(]); resp.getWriter().write(json.toString()); } }逻辑说明WebServlet注解代替了 web.xml 配置Servlet 3.0 都支持。doGet里设置响应类型和编码然后调用 DAO 查询最后拼 JSON 写回。注意字符串里的引号要转义如果学生姓名里有双引号会破坏 JSON 结构所以生产环境一定要用 JSON 库。参数说明resp.setContentType(application/json;charsetutf-8)必须放在getWriter()之前否则编码不生效前端收到中文乱码。StudentDao里用PreparedStatement防止 SQL 注入这是答辩常问点。4. 前端页面与登录鉴权JSP 渲染和 Filter 拦截4.1 登录页面与 Session 写入登录是系统的入口也是答辩演示的第一印象。登录页用 Bootstrap 快速搭一个居中表单提交到LoginServlet。!-- login.jsp -- form action${pageContext.request.contextPath}/login methodpost input typetext nameusername classform-control placeholder用户名 required input typepassword namepassword classform-control placeholder密码 required button typesubmit classbtn btn-primary w-100登录/button /form// com.example.sms.servlet.LoginServlet WebServlet(/login) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDao(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); User user userDao.findByUsername(username); if (user ! null user.getPassword().equals(password)) { // 登录成功写入 Session req.getSession().setAttribute(user, user); resp.sendRedirect(req.getContextPath() /student/list.jsp); } else { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }逻辑说明登录成功后把User对象放进 Session后续页面和过滤器都从 Session 取。密码这里用了明文比较毕设演示可以但答辩时如果老师问“密码怎么存的”你要能说出“实际应该用 MD5 加盐或 BCrypt”。sendRedirect是重定向地址栏会变forward是转发地址栏不变。参数说明req.getContextPath()返回项目部署路径比如/sms拼在 URL 前面避免路径错误。Session 默认超时 30 分钟可以在 web.xml 里改。4.2 登录过滤器防止未登录直接访问后台如果没有过滤器别人直接在浏览器输入/student/list.jsp就能进后台这是毕设演示的硬伤。写一个LoginFilter拦截所有后台请求。// com.example.sms.filter.LoginFilter WebFilter(urlPatterns {/student/*, /course/*, /score/*}) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; Object user req.getSession().getAttribute(user); if (user null) { // 未登录跳回登录页 resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }逻辑说明WebFilter的urlPatterns指定拦截路径这里拦截学生、课程、成绩相关所有请求。如果 Session 里没有 user直接重定向到登录页并且return不再放行。chain.doFilter是放行到下一个过滤器或目标资源。参数说明不要把login.jsp、login这些路径也拦截了否则会死循环。如果项目有静态资源css、js也要排除或者放在不拦截的目录下。4.3 学生列表页JSTL 循环渲染表格JSP 里用 JSTL 的c:forEach遍历后端传来的 list比 Java 脚本片段干净。% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table classtable table-striped thead trth学号/thth姓名/thth性别/thth专业/thth操作/th/tr /thead tbody c:forEach items${studentList} vars tr td${s.sno}/td td${s.name}/td td${s.gender}/td td${s.major}/td td a hrefedit.jsp?id${s.id} classbtn btn-sm btn-warning编辑/a a hrefdelete?id${s.id} classbtn btn-sm btn-danger onclickreturn confirm(确认删除)删除/a /td /tr /c:forEach /tbody /table逻辑说明${studentList}是 request 或 session 里的属性名需要在 Servlet 里req.setAttribute(studentList, list)并转发到 JSP。c:forEach的var是循环变量EL 表达式${s.sno}会自动调用 getter。参数说明删除链接加onclick确认框防止误点。编辑跳转带id参数编辑页再根据 id 查询单条记录。如果列表为空页面会显示空表格可以加一个c:if判断显示“暂无数据”。5. 避坑与排查IDEA 运行 JavaWeb 项目最容易翻车的 5 个点5.1 现象Tomcat 启动报 404所有页面都访问不到原因IDEA 的 Artifact 配置里没有把webapp目录正确部署或者 Application context 设成了/但代码里getContextPath()拼出来是空。常见于从 Eclipse 转 IDEA 的同学。解决打开 Run/Debug Configurations找到 Tomcat Server在 Deployment 选项卡里确认 Artifact 是student-management:war explodedApplication context 填/sms。然后检查web.xml的welcome-file是否是login.jsp。如果还不行看 Tomcat 日志里有没有ClassNotFoundException那是依赖没打进 Artifact。5.2 现象数据库连接报Access denied for user rootlocalhost原因db.properties里的密码和 MySQL 实际密码不一致或者 MySQL 8.0 的加密方式导致旧驱动连不上。解决先确认密码。如果密码对但还报错在 MySQL 里执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码;然后FLUSH PRIVILEGES;。这是 MySQL 8.0 默认caching_sha2_password和旧驱动不兼容的经典问题。5.3 现象JSP 页面中文乱码表单提交后数据库里是问号原因JSP 页面没有声明pageEncoding或者 Servlet 没有设置请求编码。GET 请求和 POST 请求的编码处理方式不同。解决每个 JSP 顶部加% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %。Servlet 里在doPost第一行加req.setCharacterEncoding(UTF-8);。如果是 GET 请求Tomcat 8 以后默认 URI 编码是 UTF-8但最好在 server.xml 的 Connector 里加URIEncodingUTF-8。数据库连接 URL 里也要有characterEncodingutf8。5.4 现象修改代码后刷新页面没变化还是旧逻辑原因IDEA 热部署没生效或者 Tomcat 缓存了编译后的 class。毕设演示时改完代码直接刷新发现没变容易慌。解决在 Run/Debug Configurations 里Tomcat 的 Server 选项卡把On Update action和On frame deactivation都设为Update classes and resources。如果还不行点 Build 菜单里的 Rebuild Project然后重启 Tomcat。血泪经验演示前一定要重启一次别依赖热部署。5.5 现象登录后跳转正常但点其他菜单又回到登录页原因Session 丢失。可能是浏览器禁用了 Cookie或者项目部署路径变了导致 JSESSIONID 对不上也可能是 Filter 拦截了静态资源导致页面样式加载失败看起来像“跳回登录”。解决先看浏览器地址栏有没有;jsessionidxxx如果有说明 Cookie 没生效检查浏览器设置。然后确认 Filter 的urlPatterns没有拦截/css/*、/js/*。如果用了sendRedirect跳转确保跳转路径带上了getContextPath()。最后检查 Tomcat 的conf/context.xml里有没有被加上sessionCookiePath之类的限制。6. 进阶技巧把“能跑”变成“能答辩”的三个加分项6.1 用 Druid 监控页面证明你懂连接池Druid 自带一个监控 Servlet配置后访问/druid/index.html就能看到 SQL 执行次数、连接池状态。答辩时老师问“你怎么知道连接池有没有泄漏”你直接打开监控页比说一堆理论有说服力。在web.xml里加servlet servlet-nameDruidStatView/servlet-name servlet-classcom.alibaba.druid.support.http.StatViewServlet/servlet-class init-param param-nameloginUsername/param-name param-valueadmin/param-value /init-param init-param param-nameloginPassword/param-name param-valueadmin/param-value /init-param /servlet servlet-mapping servlet-nameDruidStatView/servlet-name url-pattern/druid/*/url-pattern /servlet-mapping逻辑说明StatViewServlet是 Druid 内置的监控入口loginUsername和loginPassword是访问监控页的账号密码不设的话谁都能看。url-pattern用/druid/*避免和其他 Servlet 冲突。参数说明监控页里重点看“SQL 监控”和“数据源”两个标签。如果ActiveCount一直等于maxActive说明连接没归还检查 DAO 里有没有在 finally 块调用JdbcUtil.close。6.2 用分页查询避免“数据一多就卡”毕设演示数据少但老师可能问“如果有一万条学生记录怎么办”。你可以在StudentDao里加一个分页方法用LIMIT实现。public ListStudent findByPage(int page, int size) { String sql SELECT * FROM student LIMIT ?, ?; try (Connection conn JdbcUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, (page - 1) * size); ps.setInt(2, size); ResultSet rs ps.executeQuery(); ListStudent list new ArrayList(); while (rs.next()) { Student s new Student(); s.setId(rs.getInt(id)); s.setSno(rs.getString(sno)); s.setName(rs.getString(name)); list.add(s); } return list; } catch (SQLException e) { throw new RuntimeException(分页查询失败, e); } }逻辑说明LIMIT offset, size里offset是跳过的行数(page-1)*size就是当前页的起始位置。用 try-with-resources 自动关闭连接和语句比手动 close 更安全。参数说明page从 1 开始size是每页条数一般 10 或 20。前端传参时要做校验防止page0或负数导致 SQL 报错。如果数据量特别大LIMIT偏移量太大也会慢但毕设场景够用。6.3 一个我踩过的坑别在 JSP 里写业务逻辑刚开始做的时候我为了省事直接在 JSP 里用% %写 JDBC 查询结果页面又慢又难调改一个字段要翻三个文件。后来全部改成 Servlet 查好数据、setAttribute转发到 JSPJSP 只负责渲染。这个习惯让我在答辩时能清晰地说出“MVC 分层”到底分在哪。如果你现在代码里还有 JSP 直连数据库建议花半天时间抽出来。抽完之后你会发现调试变得简单数据不对就看 DAO页面不对就看 JSP逻辑不对就看 Servlet。这个习惯比多写两个功能更值钱。希望帮到你。本文还有配套的精品资源点击获取