☰
JSP+Servlet+MySQL教务系统源码实战:从部署到功能扩展
2026/10/9 3:51:49 网站建设 项目流程

简介:这份JSP源码实现了一套基于Java Web技术的教务管理系统,面向正在学习或准备课程设计、毕业设计的Java Web初学者与在校学生,帮助其理解JSP、Servlet与MySQL如何协同完成一个完整Web应用。压缩包共530个文件,约9.32MB,以412个gif与32个jpg界面素材为主,另含24个java源文件、24个class编译文件、10个jsp页面、9个jar依赖包及sql建表脚本、properties配置、css与xml等,覆盖前端展示、业务逻辑与数据库脚本各层。系统围绕学生信息、教师资料、课程设置与考试安排等模块展开,采用MVC思路分离视图、控制与数据访问,Servlet处理请求并调用DAO完成增删改查,JSP负责界面渲染,MySQL存储数据,并涉及连接池与JSTL、EL等常用技术点。目前已有719人学习下载,适合作为Java Web入门到进阶的实战参考,便于对照源码梳理项目结构与开发流程。

1. 从一份 JSP 教务管理系统源码说起:它到底能跑出什么

很多人第一次接触 JavaWeb,都是从一份「教务管理系统」的课程设计源码开始的。标题里这份jsp+servlet+mysql的组合,几乎是十年前高校机房里的标准配置:JSP 负责页面渲染,Servlet 接请求、调业务,MySQL 存学生、课程、成绩、教师这几张核心表。它不新,但它是理解「请求怎么从浏览器走到数据库再走回来」最短的一条路径。你如果正在做课程设计、想找一个能改能跑的 JavaWeb 完整案例,或者想拿它当 MyBatis、Spring Boot 重构前的对照基线,这份源码就是合适的起点。它解决的不是高并发,而是「一个能登录、能增删改查、能分页、能导出成绩」的闭环。适合谁?适合刚学完 Servlet 生命周期、还没把 JSP 内置对象用熟的人,也适合想回头补一补原生 JDBC 连接池、事务边界的老手。下面我按「先跑起来、再拆结构、再改功能、最后避坑」的顺序讲,每一步都落到能复现的命令和参数上。

2. 把 JSP 教务系统在本地跑起来:环境、建库与部署

2.1 环境选型:为什么是 JDK8 + Tomcat8 + MySQL5.7

这套源码的技术栈决定了它挑环境。JSP 里如果用了<%@ page language="java" %>加 scriptlet,Servlet 又直接new对象,那它对 JDK 版本其实不敏感,JDK8 足够,JDK17 反而可能因为javax.servlet包名迁移到jakarta.servlet而编译不过。Tomcat 选 8.5 或 9.0 都行,因为这两个版本还是javax.servlet.*命名空间,和源码里的import javax.servlet.http.*对得上。MySQL 用 5.7 最稳,原因是老源码里的 JDBC URL 常写成jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf8,没带serverTimezone,MySQL 8.0 驱动会直接抛时区异常。热搜里反复出现的「mysql 5.7.44 安装过程详细」「mysql安装配置教程」不是没道理,这个版本就是给这类老项目兜底的。

我一般会先把版本对齐再动手,避免后面把时间浪费在「驱动不匹配」这种玄学问题上。下面这张表是我实际用的组合,你可以直接照抄:

组件版本关键原因
JDK8u301兼容javax.servlet,无模块化限制
Tomcat8.5.81命名空间与源码一致,部署路径简单
MySQL5.7.44默认时区、驱动行为与老 JDBC 代码匹配
驱动 jarmysql-connector-java-5.1.49与 5.7 服务端握手稳定
IDEEclipse/IDEA 均可只要能配 Tomcat 运行环境

提示:如果你机器上已经装了 MySQL 8.0,不想卸载,可以在 JDBC URL 末尾补&serverTimezone=Asia/Shanghai&useSSL=false,多数连接报错能压下去,但驱动 jar 要换成 8.x。

2.2 建库与导入 SQL:三条命令搞定初始化

源码包里通常有一个.sql文件,里面是建库、建表和初始数据。别急着用图形化工具点,命令行导入更可控,出错了能看清是哪一行。先登录 MySQL:

# 登录 MySQL,-u 用户名 -p 表示随后输入密码 mysql -u root -p

登录后执行建库和导入。注意source的路径要用绝对路径,Windows 下反斜杠容易出问题,建议统一用正斜杠:

-- 创建数据库,字符集必须和源码里的连接串一致 CREATE DATABASE edu_admin DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; -- 切换到该库 USE edu_admin; -- 导入表结构和初始数据,路径按你本地实际位置改 SOURCE D:/code/edu_admin.sql;

执行完用SHOW TABLES;确认表都进来了。常见的表有student、teacher、course、score、admin这几张,字段名一般是stu_id、stu_name、course_id、score这种下划线风格。如果导入时报ERROR 1064,多半是 SQL 文件里用了 MySQL 8 才支持的语法,或者文件编码不是 UTF-8,用记事本另存为 UTF-8 再导一次。

2.3 改连接参数并部署到 Tomcat

源码里数据库连接信息通常写在一个工具类里,名字可能叫DBUtil.java或DBHelper.java,也可能直接写在某个 Servlet 的init()里。用 IDE 全局搜索jdbc:mysql就能定位。把用户名、密码、库名改成你本地的:

// DBUtil.java 中的连接常量,按本地环境修改 private static final String URL = "jdbc:mysql://localhost:3306/edu_admin?useUnicode=true&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASSWORD = "你的密码"; // 加载驱动,老代码用 Class.forName,MySQL 5.1 驱动类名如下 static { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } }

这里有两个参数值得说清楚:useUnicode=true和characterEncoding=utf8必须成对出现,只写一个在插入中文姓名时会变问号;驱动类名com.mysql.jdbc.Driver是 5.x 的写法,8.x 驱动要改成com.mysql.cj.jdbc.Driver,写错会报ClassNotFoundException。改完把项目打成 war 丢进 Tomcat 的webapps目录,或者直接在 IDE 里配置 Tomcat 运行。启动后访问http://localhost:8080/项目名/login.jsp,能看到登录页就说明部署通了。默认账号密码一般在 SQL 文件的admin表里,常见是admin/123456。

3. 拆开 JSP + Servlet + MySQL 的请求链路:一次登录到底走了几层

3.1 从 login.jsp 到 LoginServlet 的完整调用栈

理解这套源码,最有效的方式是盯住一次登录请求,看它经过哪些文件。login.jsp里通常是一个 form,action指向LoginServlet,method是 post。提交后 Tomcat 根据web.xml或@WebServlet注解把请求路由到对应 Servlet。Servlet 里拿到request.getParameter("username"),调 DAO 层查库,查到就把用户对象塞进session,然后response.sendRedirect到主页;查不到就转发回登录页并带错误信息。

// LoginServlet 核心逻辑,doPost 方法 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置编码,必须在 getParameter 之前,否则中文参数乱码 request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); // 调 DAO 查询,返回 null 表示账号或密码错误 Admin admin = adminDao.findByUsernameAndPassword(username, password); if (admin != null) { // 登录成功,把用户信息放进 session,后续页面靠它判断是否登录 request.getSession().setAttribute("admin", admin); response.sendRedirect("index.jsp"); } else { // 登录失败,带错误信息转发回登录页 request.setAttribute("msg", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); } }

这段代码里setCharacterEncoding的位置是血泪经验:它必须在第一次getParameter之前调用,写在后面等于没写。sendRedirect和forward的区别也要分清,前者是浏览器重新发请求,地址栏会变,session 能带过去;后者是服务器内部转发,地址栏不变,request 域里的msg才能被 login.jsp 读到。很多人登录失败看不到提示,就是这里用错了。

3.2 DAO 层与 JDBC 查询:PreparedStatement 为什么不能省

DAO 层是这套源码里最值得细看的地方,因为它直接暴露了原生 JDBC 的写法。好的写法用PreparedStatement加占位符,差的写法用字符串拼接 SQL。前者防注入,后者在登录框输入' or '1'='1就能绕过验证。下面是我建议你保留并理解的写法:

// AdminDao.findByUsernameAndPassword 的 JDBC 实现 public Admin findByUsernameAndPassword(String username, String password) { Admin admin = null; String sql = "SELECT id, username, password FROM admin WHERE username = ? AND password = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { // 参数按占位符顺序绑定,索引从 1 开始 ps.setString(1, username); ps.setString(2, password); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { admin = new Admin(); admin.setId(rs.getInt("id")); admin.setUsername(rs.getString("username")); } } } catch (SQLException e) { e.printStackTrace(); } return admin; }

try-with-resources保证 Connection、PreparedStatement、ResultSet 都会关闭,老代码如果写成手动close()又漏了 finally,连接池很快会被耗尽,表现就是「用一会儿就卡死」。参数绑定从 1 开始是 JDBC 规范,写 0 会抛SQLException。查询用executeQuery,增删改用executeUpdate,返回值是影响行数,这个区分在改成绩、删学生功能里会反复用到。

3.3 分页查询与 MySQL LIMIT 的配合

教务系统里学生列表、成绩列表都要分页,源码一般用LIMIT实现。Servlet 里算好当前页和每页条数,传给 DAO:

-- 查询第 2 页,每页 10 条,偏移量 = (页码-1)*每页条数 SELECT stu_id, stu_name, class_name FROM student ORDER BY stu_id LIMIT 10 OFFSET 10;

LIMIT后面跟条数,OFFSET后面跟跳过的行数。MySQL 也支持LIMIT 10, 10这种写法,逗号前是偏移量、逗号后是条数,和OFFSET语义一样但顺序相反,改代码时别搞混。热搜里的「mysql排序」在这里就是ORDER BY,分页必须带排序,否则每次查询顺序可能不同,翻页会出现重复或漏数据。总条数用SELECT COUNT(*)单独查一次,前端根据总数算总页数。

4. 在源码上改功能:新增学生、导出成绩与图片定位

4.1 新增学生:一个完整的表单到入库链路

改功能是检验你是否真读懂这套源码的最好方式。以新增学生为例,链路是addStudent.jsp表单 →AddStudentServlet→StudentDao.insert→ MySQL。表单字段要和数据库列对应,Servlet 负责收参和校验,DAO 负责执行 INSERT。

// AddStudentServlet 中收集参数并入库 request.setCharacterEncoding("UTF-8"); String stuId = request.getParameter("stuId"); String stuName = request.getParameter("stuName"); String className = request.getParameter("className"); // 简单校验,学号不能为空 if (stuId == null || stuId.trim().isEmpty()) { request.setAttribute("msg", "学号不能为空"); request.getRequestDispatcher("addStudent.jsp").forward(request, response); return; } Student stu = new Student(stuId, stuName, className); int rows = studentDao.insert(stu); // 返回影响行数 if (rows > 0) { response.sendRedirect("studentList"); // 成功后回列表页 } else { request.setAttribute("msg", "新增失败,学号可能已存在"); request.getRequestDispatcher("addStudent.jsp").forward(request, response); }

rows > 0是判断成功的标准,executeUpdate返回的就是影响行数。学号做主键时重复插入会抛SQLIntegrityConstraintViolationException,捕获后给用户友好提示,别把堆栈直接打到页面上。新增成功后用重定向而不是转发,是为了避免用户刷新页面时重复提交。

4.2 导出成绩为 Excel:用 POI 还是 CSV

成绩导出是教务系统的刚需。源码里如果没带导出功能,你可以自己加。两条路:Apache POI 生成真正的.xlsx,或者直接拼 CSV。POI 依赖重但格式规范,CSV 轻量但 Excel 打开中文可能乱码。我一般先用 CSV 快速验证,再换 POI。

// 用 CSV 导出成绩,设置响应头让浏览器下载 response.setContentType("text/csv;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment;filename=scores.csv"); PrintWriter out = response.getWriter(); out.println("学号,姓名,课程,成绩"); // 表头 List<Score> list = scoreDao.findAll(); for (Score s : list) { out.println(s.getStuId() + "," + s.getStuName() + "," + s.getCourseName() + "," + s.getScore()); } out.flush();

Content-Disposition的attachment告诉浏览器这是下载而不是直接打开。CSV 中文乱码的根因是 Excel 默认按 GBK 解析,解决办法是在文件开头写 UTF-8 BOM,或者干脆用 POI。热搜里的「jsp实现mp4视频播放」和「jsp图片如何对坐标定位」属于同类扩展需求,前者用<video>标签加正确的 MIME 类型,后者用 CSSposition:absolute配合left/top像素值,本质都是前端渲染问题,和这套后端链路不冲突。

4.3 个人信息展示页面的数据绑定

「jsp个人信息展示页面」是热搜里出现频率很高的词,落到这套源码上,就是学生登录后看自己的信息。做法是从 session 取当前用户,再查库补全字段,用 EL 表达式渲染:

<%-- profile.jsp 中展示当前登录学生的信息 --%> <%@ page contentType="text/html;charset=UTF-8" %> <% // 从 session 取登录时存入的对象 Student stu = (Student) session.getAttribute("student"); if (stu == null) { response.sendRedirect("login.jsp"); return; } %> <table> <tr><td>学号</td><td>${student.stuId}</td></tr> <tr><td>姓名</td><td>${student.stuName}</td></tr> <tr><td>班级</td><td>${student.className}</td></tr> </table>

${student.stuId}是 EL 表达式,等价于stu.getStuId(),前提是 session 里存的对象属性有对应 getter。如果页面显示空白,先检查 session 里到底存没存、属性名拼写对不对,这是最常见的翻车点。

5. 这套源码最容易踩的坑:编码、连接池与事务

5.1 中文乱码:三个位置必须同时设 UTF-8

现象:新增学生后姓名变成???,或者登录页提示文字乱码。原因:JSP 页面编码、request 编码、数据库连接编码三者不一致。解决:JSP 顶部写<%@ page contentType="text/html;charset=UTF-8" %>,Servlet 里request.setCharacterEncoding("UTF-8")放在取参之前,JDBC URL 带characterEncoding=utf8,数据库和表用utf8字符集。四处对齐,乱码基本消失。GET 请求的参数编码由 Tomcat 的server.xml里Connector的URIEncoding决定,老版本默认不是 UTF-8,需要显式加上。

5.2 连接泄漏:为什么系统跑一会儿就卡死

现象:连续操作几十次后页面一直转圈,重启 Tomcat 才恢复。原因:DAO 里 Connection 没关,或者关了 ResultSet 没关,连接池被占满。解决:统一用try-with-resources,把 Connection、PreparedStatement、ResultSet 都放进 try 的括号里。如果源码是手动关闭,检查每个close()是否在 finally 里,且顺序是 ResultSet → Statement → Connection。热搜里的「mysql高并发解决方案」在这套源码层面就是连接池配置,加一个 Druid 或 C3P0,把最大连接数设成 20 到 50 之间,别用默认值。

5.3 事务边界:改成绩和删学生为什么要一起提交

现象:删除学生成功了,但该学生的成绩记录还在,数据不一致。原因:两个 DELETE 分属不同连接,各自自动提交,第一个成功第二个失败就脏了。解决:把两个操作放进同一个 Connection,关掉自动提交,手动 commit 或 rollback:

Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交,开启事务 studentDao.deleteById(conn, stuId); scoreDao.deleteByStuId(conn, stuId); conn.commit(); // 两步都成功才提交 } catch (SQLException e) { if (conn != null) conn.rollback(); // 任一步失败全部回滚 e.printStackTrace(); } finally { if (conn != null) try { conn.close(); } catch (SQLException ignored) {} }

注意 DAO 方法要接收外部传入的 Connection,不能自己再getConnection(),否则事务管不到。setAutoCommit(false)之后忘记 commit 是另一个常见坑,数据看着像没存进去,其实是没提交。

5.4 SQL 注入与密码明文:两个必须修的安全问题

现象:登录框输入admin' --就能进后台。原因:SQL 用字符串拼接。解决:全部换成PreparedStatement占位符。另一个问题是密码明文存库,源码里admin表的 password 字段直接是123456。改进方式是用 MD5 或 BCrypt 存哈希,登录时把输入哈希后比对。热搜里的「mysql锁的分类」「mysql事务处理」属于进阶话题,这套源码用不到,但理解行锁和间隙锁对以后做高并发选课系统有帮助。

6. 从 JSP 到 Spring Boot:这套源码值不值得继续投入

这套源码最大的价值不是它本身,而是它给你一个可以对照的「旧世界」。你把它跑通、改过、踩过坑之后,再去看 Spring Boot + MyBatis 的写法,会明白那些注解和自动配置到底替你省了什么。我的习惯是:先在这套源码上把登录、分页、事务三个功能手写一遍,然后新建一个 Spring Boot 项目,把同样的功能用@RestController、@Mapper、@Transactional重写一遍,两相对比,理解会深很多。

具体怎么验证你真正掌握了?给你一个可操作的检查表:

验证项达标标准
请求链路能口述 login.jsp 到 MySQL 经过的每个类和方法
编码新增中文姓名不乱码,GET/POST 都正常
事务删学生时成绩同步删除,中途报错能回滚
安全登录框输入' or '1'='1无法绕过
分页翻到最后一页不重复、不丢数据

如果这五项都过了,这套源码对你来说就榨干了。接下来可以做的进阶方向:把 DAO 层换成 MyBatis,体会 XML 映射和注解映射的差别;把 JSP 换成 Thymeleaf 或前后端分离,理解视图层解耦;给连接池加监控,看活跃连接数和等待时间。热搜里的「springboot4」「mybatis」「javaweb项目完整案例mysql」指向的就是这条演进路线,但别一上来就跳,原生 JDBC 的坑踩过一遍,后面用框架才知道它在帮你兜什么底。

我自己的教训是:当年第一次跑这类源码,卡在 MySQL 8 的时区报错上整整一个下午,最后发现只是 URL 少了一个参数。从那以后我养成了一个习惯,任何老项目先对齐版本表,再动手写代码。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询