简介:这是一套基于JavaWeb的学生实习管理系统完整项目源码,面向计算机相关专业正在做毕设的学生以及需要项目实战练习的Java学习者,可直接作为毕业设计使用。系统采用B/S结构,后台以JSP、Servlet、JDBC为核心框架,MySQL作为数据库,开发环境为JDK、Eclipse与Tomcat,涵盖学生、老师、管理员三种角色:学生可管理实习日志与实习反馈,老师可对学生实习情况添加与查看评价,管理员则负责班级、学生、老师、通知及实习信息的新增、查询与删除,并可修改个人登录密码,功能完善、界面美观、操作简单。资源包共3个文件,包含项目源码压缩包、db_shixi.sql数据库脚本以及项目说明文档,整体约4.88MB,结构清晰,导入即可运行。项目经过严格调试,确保可正常启动,已有3372人学习下载,适合需要快速搭建实习实训管理系统、理解JavaWeb分层开发与数据库设计的读者参考借鉴。
1. 从一份 JSP+Servlet 的实习管理系统源码说起
很多同学做毕设时,第一反应是上 SpringBoot+Vue,结果环境还没配好就卡在依赖冲突上。其实如果你的选题是「学生实习管理系统」这类偏信息管理的题目,一套 JSP+Servlet+JDBC 的经典 JavaWeb 架构反而更稳——没有复杂的自动配置,没有前后端分离的跨域问题,Tomcat 一跑就能看到页面。这份资源就是这样一个项目:源码、数据库脚本、项目说明打包在一起,三种角色(学生、老师、管理员)的功能模块都写全了。它适合谁?适合正在做毕设、需要一套能跑起来、能改、能写论文的 JavaWeb 项目的人,也适合刚学完 Servlet 想找个完整案例练手的初学者。下面我从环境搭建讲到功能改造,把这份源码拆开给你看。
2. 环境搭建与项目导入:JDK、Tomcat、Eclipse 三件套怎么配
2.1 为什么这套技术栈对毕设反而友好
先说说选型逻辑。JSP+Servlet+JDBC 是 JavaWeb 的「裸写」方式,不依赖 Spring 容器,不需要 Maven 拉一堆 jar 包。对于毕设场景,这意味着三件事:第一,答辩时老师问「你的请求怎么处理的」,你可以直接指着 Servlet 的doGet/doPost说清楚,不用解释 SpringMVC 的 DispatcherServlet 怎么转发;第二,环境搭建简单,装个 JDK、解压个 Tomcat、导入 Eclipse 就能跑,不会出现「Maven 依赖下载失败」这种让人崩溃的问题;第三,代码结构直白,src下按包分层,WebContent下放 JSP,数据库操作就是 JDBC 的Connection、PreparedStatement、ResultSet三板斧,改起来没有心理负担。
当然,这套架构也有边界。它不适合高并发场景,连接池需要自己配,SQL 注入要靠PreparedStatement手动防。但毕设系统用户量就几十个,这些都不是问题。我一般会建议:如果你的题目是「XX 管理系统」,且学校没强制要求 SpringBoot,JSP+Servlet 是性价比最高的选择。
2.2 导入前的环境检查清单
在导入项目之前,先把这三样东西对齐版本,否则后面报错会浪费大量时间。
| 组件 | 推荐版本 | 检查方式 | 常见问题 |
|---|---|---|---|
| JDK | 1.8 | java -version | 高版本 JDK 编译低版本项目可能报错 |
| Tomcat | 8.5 或 9.0 | 启动后访问localhost:8080 | Tomcat 10 的 Servlet 包名变了,老项目跑不了 |
| MySQL | 5.7 或 8.0 | mysql -u root -p | 8.0 需要改连接串的时区和驱动类 |
| Eclipse | 2019 及以上 | 能创建 Dynamic Web Project | 需确认已装 JST Server Adapters |
这里重点说 Tomcat 版本。Tomcat 10 把javax.servlet改成了jakarta.servlet,这份源码用的是javax包,所以必须用 Tomcat 9 或更低版本。我见过有人装了 Tomcat 10 后所有 Servlet 报 404,排查半天才发现是包名问题。JDK 同理,1.8 最稳,高版本编译时可能遇到Unsupported class file major version之类的错误。
2.3 导入 Eclipse 并配置 Tomcat 的完整步骤
假设你已经装好 JDK 1.8、Tomcat 9、MySQL 5.7,接下来按步骤走。
第一步,解压项目包。你会看到shixi.zip(源码压缩包)和db_shixi.sql(数据库脚本)。先把shixi.zip解压到一个不含中文和空格的路径,比如D:\workspace\shixi。
第二步,在 Eclipse 中导入项目。菜单File → Import → General → Existing Projects into Workspace,选择解压后的shixi目录。如果 Eclipse 提示没有.project文件,说明这个项目不是 Eclipse 工程格式,需要手动创建 Dynamic Web Project 再把文件拷进去。
第三步,配置 Tomcat 运行时。右键项目 →Properties → Targeted Runtimes,勾选你安装的 Tomcat 9。然后Properties → Java Build Path → Libraries,确认 JRE 是 1.8,并且有Apache Tomcat v9.0的库。
第四步,检查WebContent/WEB-INF/lib下是否有mysql-connector-java-x.x.x.jar。如果没有,需要自己下载对应版本的驱动放进去,并Add to Build Path。
第五步,启动 Tomcat。右键项目 →Run As → Run on Server,选择 Tomcat 9,启动后浏览器访问http://localhost:8080/shixi/。如果看到登录页,说明部署成功。
# 数据库导入命令,在命令行执行 mysql -u root -p -e "CREATE DATABASE shixi DEFAULT CHARACTER SET utf8mb4;" mysql -u root -p shixi < db_shixi.sql这段命令做了两件事:先创建名为shixi的数据库并指定字符集为utf8mb4(防止中文乱码),然后把db_shixi.sql里的建表和初始数据导入进去。注意db_shixi.sql里可能已经包含CREATE DATABASE语句,如果执行时报「database exists」,可以先打开 sql 文件确认一下,把重复的建库语句删掉再导入。
2.4 数据库连接配置的修改位置
导入数据库后,还要改代码里的连接串。在src目录下找db或util包,通常会有一个DBUtil.java或DBHelper.java。打开后找到类似下面的代码:
// 数据库连接配置,根据自己环境修改 private static final String URL = "jdbc:mysql://localhost:3306/shixi?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"; private static final String USER = "root"; private static final String PASSWORD = "123456"; private static final String DRIVER = "com.mysql.cj.jdbc.Driver";参数说明:URL里的shixi是数据库名,useUnicode=true&characterEncoding=utf8保证中文不乱码,serverTimezone=Asia/Shanghai是 MySQL 8.0 必须加的时区参数,不加会报The server time zone value is unrecognized。DRIVER如果是 MySQL 5.7,用com.mysql.jdbc.Driver;8.0 用com.mysql.cj.jdbc.Driver。改完保存,重启 Tomcat 即可。
3. 三种角色的功能模块拆解与代码走读
3.1 学生角色:实习日志与反馈的增查实现
学生登录后主要用两个模块:实习日志管理和实习反馈管理。这两个模块的逻辑几乎一样,都是「添加 + 查看」,属于典型的 CRUD 中的 Create 和 Read。
先看实习日志的添加流程。前端 JSP 页面addLog.jsp里有一个表单,提交到LogServlet。Servlet 的doPost方法接收参数,调用 DAO 层插入数据库。
// LogServlet.java 中处理添加日志的核心逻辑 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 防止中文参数乱码 String title = request.getParameter("title"); String content = request.getParameter("content"); String date = request.getParameter("date"); int studentId = (int) request.getSession().getAttribute("userId"); // 从 session 取当前学生 ID Log log = new Log(); log.setTitle(title); log.setContent(content); log.setDate(date); log.setStudentId(studentId); LogDao logDao = new LogDao(); boolean success = logDao.addLog(log); // 调用 DAO 插入 if (success) { response.sendRedirect("logList.jsp"); // 添加成功跳回列表 } else { request.setAttribute("msg", "添加失败,请重试"); request.getRequestDispatcher("addLog.jsp").forward(request, response); } }逻辑说明:request.setCharacterEncoding("UTF-8")必须放在获取参数之前,否则中文会乱码。studentId从 session 里取,而不是从表单传,这样避免学生篡改 ID 查看别人数据。DAO 层用PreparedStatement执行INSERT,返回布尔值表示是否成功。
查看日志的 DAO 方法:
// LogDao.java 中查询某学生所有日志 public List<Log> getLogsByStudentId(int studentId) { List<Log> list = new ArrayList<>(); String sql = "SELECT * FROM t_log WHERE student_id = ? ORDER BY date DESC"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, studentId); ResultSet rs = ps.executeQuery(); while (rs.next()) { Log log = new Log(); log.setId(rs.getInt("id")); log.setTitle(rs.getString("title")); log.setContent(rs.getString("content")); log.setDate(rs.getString("date")); list.add(log); } } catch (SQLException e) { e.printStackTrace(); } return list; }这里用try-with-resources自动关闭连接,避免忘记close()导致连接泄漏。ORDER BY date DESC让最新的日志排在最前面。参数studentId通过ps.setInt(1, studentId)传入,防止 SQL 注入。
实习反馈模块的代码结构完全一致,只是表名从t_log换成t_feedback,字段略有不同。你可以照着日志模块自己改,或者直接复用。
3.2 老师角色:实习评价的录入与关联查询
老师登录后主要功能是给学生写实习评价。这个模块比学生端多了一个「关联查询」——老师需要先看到自己指导的学生列表,再对某个学生写评价。
评价添加的 Servlet 逻辑:
// EvaluateServlet.java 处理评价提交 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); int studentId = Integer.parseInt(request.getParameter("studentId")); int teacherId = (int) request.getSession().getAttribute("userId"); String content = request.getParameter("content"); String score = request.getParameter("score"); Evaluate eval = new Evaluate(); eval.setStudentId(studentId); eval.setTeacherId(teacherId); eval.setContent(content); eval.setScore(score); EvaluateDao evalDao = new EvaluateDao(); if (evalDao.addEvaluate(eval)) { response.sendRedirect("evalList.jsp"); } else { request.setAttribute("msg", "评价提交失败"); request.getRequestDispatcher("addEval.jsp").forward(request, response); } }关键点:teacherId从 session 取,studentId从表单取。为什么studentId可以从表单传?因为老师选择的是自己指导的学生,这个列表是后端根据teacherId查出来的,学生无法伪造。但更严谨的做法是在插入前再校验一次「该学生是否属于该老师」,防止有人手动改表单参数。
查询评价列表时,需要关联学生表获取学生姓名:
-- 查询某老师的所有评价,关联学生表拿姓名 SELECT e.*, s.name AS student_name FROM t_evaluate e LEFT JOIN t_student s ON e.student_id = s.id WHERE e.teacher_id = ? ORDER BY e.id DESC;这条 SQL 用LEFT JOIN关联学生表,这样即使学生信息被删除,评价记录也不会丢失。AS student_name是给结果集列起别名,方便在 JSP 里用${item.studentName}取值。
3.3 管理员角色:五个管理模块的通用套路
管理员的功能最多:班级管理、学生管理、老师管理、通知管理、实习信息管理。这五个模块的代码结构高度相似,都是「列表 + 添加 + 删除 + 查询」。你只要搞懂一个,剩下的就是复制粘贴改表名。
以学生管理为例,删除操作的 Servlet:
// StudentServlet.java 中删除学生的逻辑 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = request.getParameter("action"); if ("delete".equals(action)) { int id = Integer.parseInt(request.getParameter("id")); StudentDao studentDao = new StudentDao(); boolean success = studentDao.deleteStudent(id); response.sendRedirect("studentList.jsp?msg=" + (success ? "删除成功" : "删除失败")); } }这里用action参数区分不同操作,是 JavaWeb 里常见的「一个 Servlet 处理多个动作」的写法。删除成功后通过 URL 参数msg传递提示信息,列表页读取后显示。
查询功能通常带条件:
// StudentDao.java 中按姓名模糊查询 public List<Student> searchStudents(String keyword) { List<Student> list = new ArrayList<>(); String sql = "SELECT * FROM t_student WHERE name LIKE ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, "%" + keyword + "%"); ResultSet rs = ps.executeQuery(); while (rs.next()) { Student s = new Student(); s.setId(rs.getInt("id")); s.setName(rs.getString("name")); s.setClassName(rs.getString("class_name")); list.add(s); } } catch (SQLException e) { e.printStackTrace(); } return list; }LIKE ?配合%keyword%实现模糊查询。注意setString里的%要拼在 Java 代码里,不能写在 SQL 里写成LIKE '%?%',那样占位符不会被替换。
通知管理和实习信息管理的代码几乎一样,只是表名和字段不同。如果你要扩展功能,比如加一个「实习成绩统计」,可以在这个基础上加一个 Servlet 和 DAO 方法,查询时用GROUP BY聚合。
4. 避坑与排查:导入运行中最容易翻车的五个点
4.1 中文乱码:从数据库到页面的全链路排查
现象:添加日志后,列表页显示「测试」这样的乱码。
原因:乱码可能出现在三个环节——数据库字符集、JDBC 连接串、JSP 页面编码。任何一处不是 UTF-8 都会出问题。
解决:按顺序检查。第一,数据库和表的字符集用SHOW CREATE TABLE t_log;确认是utf8mb4。第二,JDBC 连接串加上useUnicode=true&characterEncoding=utf8。第三,每个 JSP 页面顶部加<%@ page contentType="text/html;charset=UTF-8" language="java" %>。第四,Servlet 里request.setCharacterEncoding("UTF-8")必须在getParameter之前调用。四个地方都对了,乱码才会消失。
4.2 404 错误:路径与 Tomcat 版本的常见陷阱
现象:启动 Tomcat 后访问http://localhost:8080/shixi/报 404。
原因:可能是项目没有部署到 Tomcat,或者访问路径不对,或者 Tomcat 版本不兼容。
解决:先在 Eclipse 的 Servers 视图里确认项目已添加。然后右键项目 →Properties → Web Project Settings,看Context root是不是shixi。如果 Tomcat 是 10.x,换成 9.x,因为javax.servlet包在 Tomcat 10 里不存在了。还有一种情况是web.xml里配的url-pattern和实际访问路径不一致,打开WEB-INF/web.xml核对一下。
4.3 数据库连接失败:驱动类与端口占用
现象:启动后访问登录页,提交表单报java.sql.SQLException: No suitable driver found。
原因:MySQL 驱动 jar 没放到WEB-INF/lib下,或者驱动类名写错了。
解决:确认WEB-INF/lib下有mysql-connector-java-5.1.x.jar(MySQL 5.7)或mysql-connector-java-8.0.x.jar(MySQL 8.0)。然后检查DBUtil.java里的DRIVER变量:5.7 用com.mysql.jdbc.Driver,8.0 用com.mysql.cj.jdbc.Driver。如果还报错,看控制台有没有Communications link failure,那说明 MySQL 服务没启动或端口不是 3306。
4.4 修改密码后登录失效:Session 与数据库不同步
现象:管理员修改密码后,退出重新登录,新密码登不上,旧密码也不行。
原因:修改密码的 SQL 可能没执行成功,或者更新时用了错误的用户 ID。
解决:打开ChangePasswordServlet,检查UPDATE t_admin SET password = ? WHERE id = ?里的id是不是从 session 取的当前用户 ID。如果是从表单取的,可能被篡改。另外确认executeUpdate()的返回值是不是大于 0,如果等于 0 说明没更新到任何行。还有一种可能是密码做了 MD5 加密,修改时忘了加密,登录时却按加密后的比对。
4.5 列表分页失效:SQL LIMIT 与页码计算
现象:数据多了之后列表页只显示前几条,翻页没反应。
原因:这份源码可能没做分页,或者分页的LIMIT参数算错了。
解决:如果原项目没分页,可以自己加。核心 SQL 是SELECT * FROM t_student LIMIT ?, ?,第一个参数是偏移量(pageNum - 1) * pageSize,第二个是每页条数。Servlet 里接收pageNum参数,默认 1。JSP 页面底部加「上一页/下一页」链接,传递pageNum。注意LIMIT的两个参数都必须是整数,不能加引号。
5. 从能跑到能答辩:二次开发与论文素材提取技巧
5.1 给系统加一个「实习统计」模块
答辩时老师常问「你的系统有什么亮点」。如果只是增删改查,确实单薄。我一般会建议加一个统计模块,用GROUP BY做聚合查询,代码量不大但效果明显。
// StatDao.java 统计每个班级的实习人数 public List<Map<String, Object>> countByClass() { List<Map<String, Object>> list = new ArrayList<>(); String sql = "SELECT c.name AS class_name, COUNT(s.id) AS student_count " + "FROM t_class c LEFT JOIN t_student s ON c.id = s.class_id " + "GROUP BY c.id, c.name"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ResultSet rs = ps.executeQuery(); while (rs.next()) { Map<String, Object> map = new HashMap<>(); map.put("className", rs.getString("class_name")); map.put("studentCount", rs.getInt("student_count")); list.add(map); } } catch (SQLException e) { e.printStackTrace(); } return list; }这段代码用LEFT JOIN保证没有学生的班级也显示,COUNT(s.id)统计学生数。返回List<Map>而不是实体类,是因为统计结果的字段不固定,用 Map 更灵活。JSP 页面用<c:forEach>遍历显示成表格,就是一个「班级实习人数统计」功能。
5.2 论文里怎么描述这套系统的技术架构
写论文时,不要只贴代码。我一般会按这个结构写:先画一个 B/S 架构图(用 Visio 或 draw.io),说明浏览器、Tomcat、MySQL 三层的关系。然后写「系统采用 JSP+Servlet+JDBC 的经典 JavaWeb 架构,JSP 负责视图展示,Servlet 负责请求分发和业务逻辑,JDBC 负责数据持久化」。接着写「数据库设计遵循第三范式,共 X 张表,包括学生表、老师表、班级表、日志表、评价表等」,把表结构用表格列出来。最后写「系统实现了三种角色的权限隔离,通过 Session 存储登录状态,不同角色登录后跳转到不同的功能主页」。
这样写既体现了技术选型,又展示了工作量,比单纯说「我用了 JavaWeb」有说服力得多。
5.3 答辩前必须走一遍的自检清单
从那以后我每次帮人看毕设项目,都强制走一遍这个流程:第一步,删掉 Tomcat 里的部署,重新Run on Server,确认从零启动能跑通。第二步,用三个角色分别登录,每个功能点一遍,特别是添加和删除。第三步,把数据库导出成 SQL 文件,删库重建,再导入一次,确认脚本完整。第四步,改一个 JSP 页面的标题,重新部署,确认修改生效。第五步,打开浏览器控制台,看有没有 JS 报错或 404 的资源请求。这五步走完,基本不会在答辩现场翻车。
希望帮到你。
本文还有配套的精品资源,点击获取