☰
JavaWeb学籍管理系统实战:Servlet+JSP+JDBC从配置到事务避坑
2026/9/28 1:53:27 网站建设 项目流程

简介:这是一套基于JavaWeb的学籍管理系统完整源码与数据库文件,面向高校计算机相关专业学生、课程设计或期末大作业开发者,帮助解决学籍信息管理类项目的搭建与实现问题。资源包共50个文件,约2.43MB,包含21个java源文件、13个class编译文件、6个jar依赖包、4个sql数据库脚本,以及项目配置与说明文档,覆盖从源码到数据库的完整结构。项目经过严格调试,评审分达到95分以上,可直接运行使用。读者可获取系统核心业务逻辑、数据库建表脚本、依赖库配置及项目目录组织方式,便于快速理解JavaWeb开发流程、对照修改功能模块或作为课程设计参考模板。目前已有175人学习下载,适合需要完整项目案例进行学习与二次开发的学生参考。

1. 从一份“学籍管理系统”压缩包说起:JavaWeb 期末大作业到底在考什么

每年期末季,总有一批计算机专业的学生在深夜对着一个名为“基于javaweb的学籍管理系统源码+数据库文件(期末大作业).zip”的压缩包发愁。解压之后,里面通常躺着几个文件夹:src、WebContent、sql 脚本、lib 依赖,还有一份不知道能不能跑起来的 README。这个场景太典型了——它不是某个高深的研究课题,而是一个标准的 JavaWeb 综合实训项目,核心目标只有一个:用 Servlet + JSP + JDBC 这套经典组合,把学生、课程、成绩、班级这几张表之间的增删改查和关联逻辑跑通。

学籍管理系统之所以成为 JavaWeb 课程设计的“钉子户”,是因为它天然覆盖了 Web 开发的全链路:前端表单提交、后端请求分发、业务逻辑处理、数据库持久化、会话状态管理。你在这个项目里踩过的每一个坑——中文乱码、连接池泄漏、事务不回滚、分页算错——在真实企业项目里都会以另一种形式重现。所以别把它当成一次交差,它其实是你从“会写 Hello World”到“能交付一个完整 CRUD 系统”之间最短的那座桥。

这篇文章面向三类人:正在做期末大作业、需要一份能跑通的参考实现的学生;刚学完 Servlet 想找个完整案例练手的自学者;以及需要快速搭一个后台管理原型、不想从零写权限和分页的开发者。我会按“环境搭建 → 数据库设计 → 核心功能实现 → 避坑排查 → 进阶技巧”的顺序,把这份压缩包背后的技术栈拆开讲透。你不需要有 Spring Boot 经验,但需要会基本的 Java 语法和 SQL。读完你至少能得到一个能本地运行、能改需求、能讲清楚每一行为什么这么写的学籍管理系统。

2. 把压缩包跑起来:IDEA 配置 JavaWeb 项目与 MySQL 建库的完整路径

2.1 先看清项目结构再动手,别急着点运行

拿到压缩包后第一件事不是双击 index.jsp,而是先判断它属于哪种 JavaWeb 项目形态。常见的有两种:一种是 Eclipse 动态 Web 项目结构,根目录下有 WebContent 或 WebRoot;另一种是 Maven 结构,有 pom.xml 和 src/main/webapp。期末大作业里前者占多数,因为很多教材还在用 Eclipse 或 MyEclipse 演示。你需要确认三件事:JDK 版本(看编译级别,一般是 1.8)、Servlet 版本(看 web.xml 的 schema 或注解)、MySQL 驱动版本(看 lib 下的 jar 包名,mysql-connector-java-5.x 还是 8.x)。

如果是 Eclipse 结构,在 IDEA 里不能直接 Open,要选“Import Project”然后指定为 Eclipse 项目,或者更稳妥的做法是新建一个 Java Enterprise 项目,把 src 和 WebContent 分别拷贝进去。这里有个血泪经验:直接拷贝 WebContent 时,WEB-INF 下的 classes 目录经常是空的,因为 Eclipse 默认把编译输出指向了别处。你需要手动在 IDEA 的 Project Structure 里把输出路径设到 WEB-INF/classes,否则 Tomcat 启动后必然报 ClassNotFoundException。

# 查看压缩包内结构(Linux/macOS) unzip -l "基于javaweb的学籍管理系统源码+数据库文件(期末大作业).zip" # 典型输出会包含: # src/com/student/dao/StudentDao.java # WebContent/WEB-INF/web.xml # WebContent/index.jsp # db/student_manager.sql # lib/mysql-connector-java-5.1.47.jar

上面这条命令帮你快速定位数据库脚本和依赖包的位置。参数说明:-l只列出内容不解压,避免污染当前目录。如果你看到 lib 下同时有多个版本的 MySQL 驱动,只保留一个,版本要和你的 MySQL 服务器匹配——MySQL 5.7 用 5.1.x 驱动,MySQL 8.0 用 8.0.x 驱动,混用会报“Unknown system variable ‘query_cache_size’”这类玄学错误。

2.2 数据库建库建表:从 sql 文件到可连接的数据源

压缩包里的 .sql 文件通常包含建库、建表、插入测试数据三部分。但很多同学直接 source 进去就完事,结果项目连不上,因为脚本里的库名和代码里 JDBC URL 的库名不一致。正确做法是先打开 sql 文件,找到CREATE DATABASE和USE语句,记下库名,再去看代码里的jdbc:mysql://localhost:3306/xxx。

-- 先创建数据库并指定字符集,避免中文姓名变问号 CREATE DATABASE student_manager DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE student_manager; -- 学生表:学号做主键,班级和专业做外键关联 CREATE TABLE student ( student_id VARCHAR(20) PRIMARY KEY, name VARCHAR(50) NOT NULL, gender ENUM('男','女') DEFAULT '男', birth_date DATE, class_id INT, major_id INT, phone VARCHAR(20), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 成绩表:联合主键防止同一学生同一课程重复录入 CREATE TABLE score ( student_id VARCHAR(20), course_id INT, score DECIMAL(5,2), exam_date DATE, PRIMARY KEY (student_id, course_id), FOREIGN KEY (student_id) REFERENCES student(student_id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

建表时有两个参数值得注意。一是utf8mb4而不是utf8,因为 MySQL 的 utf8 是阉割版,存不了 emoji 和部分生僻字,学籍系统里偶尔会有少数民族姓名用字,用 utf8mb4 省心。二是ON DELETE CASCADE,删除学生时自动清理其成绩记录,避免孤儿数据。但要注意,如果业务上要求保留历史成绩,就不能用 CASCADE,而应该在应用层做软删除。期末作业里为了演示方便用 CASCADE 没问题,但面试时被问到“为什么不用物理删除”你要能答上来。

2.3 IDEA 运行配置:Tomcat 部署与 JDBC 连接测试

数据库准备好后,回到 IDEA 配置 Tomcat。Run → Edit Configurations → 加号 → Tomcat Server → Local。关键在 Deployment 标签页:点加号选 Artifact,通常选xxx:war exploded,这样支持热更新。Application context 建议设成/student,不要留空,否则访问路径是根目录容易和别的项目冲突。

// 数据库连接工具类,放在 src/com/student/util/DBUtil.java public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/student_manager?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai"; private static final String USER = "root"; private static final String PASSWORD = "你的密码"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); // MySQL 8 驱动类名 } catch (ClassNotFoundException e) { throw new RuntimeException("MySQL 驱动加载失败", e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }

这段代码里 URL 的参数每一个都有用。useUnicode=true&characterEncoding=utf8解决中文乱码;useSSL=false去掉启动时的 SSL 警告;serverTimezone=Asia/Shanghai是 MySQL 8 驱动必须的,不写会报时区错误。如果你用的是 MySQL 5.1 驱动,类名是com.mysql.jdbc.Driver,且不需要 serverTimezone。写完后写一个 main 方法调getConnection(),能打印出连接对象就说明数据源通了,再去写 Servlet 会少很多干扰。

3. 学籍管理系统的核心功能拆解:Servlet 分发、JSP 渲染与 JDBC 事务

3.1 用 Servlet + Service + DAO 三层把增删改查写清楚

很多期末项目把业务逻辑全写在 JSP 的 scriptlet 里,这是最容易被扣分的做法。正确的分层是:Servlet 负责接收请求和转发,Service 负责业务规则(比如学号格式校验、成绩范围校验),DAO 负责 SQL 执行。以“添加学生”为例,Servlet 拿到表单参数后不直接 insert,而是先调 Service,Service 校验通过再调 DAO。

// StudentServlet.java 中的 doPost 片段 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 必须在 getParameter 之前调用 String action = request.getParameter("action"); if ("add".equals(action)) { Student s = new Student(); s.setStudentId(request.getParameter("studentId")); s.setName(request.getParameter("name")); s.setGender(request.getParameter("gender")); // 业务校验交给 Service String result = studentService.addStudent(s); if ("success".equals(result)) { response.sendRedirect("student?action=list"); } else { request.setAttribute("error", result); request.getRequestDispatcher("/error.jsp").forward(request, response); } } }

逻辑说明:request.setCharacterEncoding("UTF-8")必须放在第一个getParameter之前,否则 POST 中文参数已经按 ISO-8859-1 解码了,再设也没用。这是新手最常翻车的地方。sendRedirect和forward的区别也要清楚:重定向会改变浏览器地址栏,适合增删改后防止刷新重复提交;转发地址栏不变,适合携带错误信息回显。参数action是前后端约定的操作码,比用多个 Servlet 更简洁,但 action 多了以后 if-else 会很长,进阶做法是用反射或简单工厂,期末作业里 if-else 够用。

3.2 分页查询与多条件搜索:别在 JSP 里写 SQL 拼接

学籍系统里学生列表通常要分页,还要支持按姓名、班级、学号模糊搜索。分页的核心是LIMIT offset, size,offset 由当前页码和每页条数算出。搜索条件要用 PreparedStatement 的?占位,不能字符串拼接,否则既有 SQL 注入风险,又会在中文条件时出乱码。

// StudentDao.java 分页查询方法 public List<Student> findByPage(String keyword, int page, int size) { List<Student> list = new ArrayList<>(); String sql = "SELECT * FROM student WHERE name LIKE ? OR student_id LIKE ? ORDER BY student_id LIMIT ?, ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { String like = "%" + (keyword == null ? "" : keyword) + "%"; ps.setString(1, like); ps.setString(2, like); ps.setInt(3, (page - 1) * size); ps.setInt(4, size); ResultSet rs = ps.executeQuery(); while (rs.next()) { Student s = new Student(); s.setStudentId(rs.getString("student_id")); s.setName(rs.getString("name")); list.add(s); } } catch (SQLException e) { e.printStackTrace(); } return list; }

参数说明:page从 1 开始,size是每页条数,通常设 10 或 15。(page - 1) * size这个公式如果写成page * size,第一页就会跳过前 size 条数据,这是分页最常见的 off-by-one 错误。另外try-with-resources保证 Connection 和 PreparedStatement 自动关闭,比手动 finally 里 close 更安全。如果你在 JSP 里用 scriptlet 写SELECT * FROM student WHERE name LIKE '%${param.name}%',不仅会被扣分,还可能在演示时被输入' OR '1'='1直接拖库,场面非常尴尬。

3.3 成绩录入的事务控制:一个学生多门课要么全成功要么全回滚

成绩录入是学籍系统里唯一需要事务的场景。一个学生一次选多门课,提交后要往 score 表插多条记录。如果插到第三条时数据库连接断了,前两条已经提交,数据就不一致了。正确做法是把 Connection 的 autoCommit 设为 false,全部成功再 commit,任何一条失败就 rollback。

// ScoreService.java 批量录入成绩 public boolean batchAddScore(String studentId, Map<Integer, Double> courseScores) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 String sql = "INSERT INTO score(student_id, course_id, score) VALUES(?, ?, ?)"; try (PreparedStatement ps = conn.prepareStatement(sql)) { for (Map.Entry<Integer, Double> entry : courseScores.entrySet()) { ps.setString(1, studentId); ps.setInt(2, entry.getKey()); ps.setDouble(3, entry.getValue()); ps.addBatch(); } ps.executeBatch(); } conn.commit(); return true; } catch (SQLException e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { if (conn != null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

这里用addBatch+executeBatch比循环单条 insert 快很多,数据量大时差距明显。关键点是 rollback 必须放在 catch 里,且 rollback 本身也可能抛异常,要再包一层 try。finally 里恢复 autoCommit 再 close,是因为连接如果来自连接池,不恢复状态会影响下一个使用者。期末作业里如果没用连接池,直接 DriverManager 获取,close 就真的关了,但养成恢复 autoCommit 的习惯对以后用 Druid、HikariCP 有好处。

4. 中文乱码、连接泄漏、事务失效:学籍管理系统最常见的五类翻车现场

4.1 现象:学生姓名存进数据库变成“???”或“å¼ ä¸‰”

原因:字符集在三个环节中至少有一个没对齐。一是数据库和表的字符集不是 utf8mb4;二是 JDBC URL 没加 characterEncoding=utf8;三是 Tomcat 的 POST 请求体默认按 ISO-8859-1 解码,而request.setCharacterEncoding没在 getParameter 之前调用。GET 请求的乱码则要在 Tomcat 的 server.xml 里给 Connector 加URIEncoding="UTF-8"。

解决:按“数据库 → 连接串 → 请求解码 → 响应编码”的顺序逐一排查。数据库用SHOW VARIABLES LIKE 'character%'确认;连接串加上useUnicode=true&characterEncoding=utf8;Servlet 第一行写request.setCharacterEncoding("UTF-8");JSP 页面顶部写<%@ page contentType="text/html;charset=UTF-8" %>。四个都对了,中文就不会再出问题。

4.2 现象:项目跑一会儿就报“Too many connections”

原因:DAO 里每次操作都DriverManager.getConnection(),但异常路径下没有 close,或者 close 写在 try 里而异常跳过了。更隐蔽的是 ResultSet 和 Statement 没关,虽然关 Connection 通常会连带关闭,但某些驱动实现下不保证。

解决:统一用 try-with-resources,把 Connection、PreparedStatement、ResultSet 都放在 try 的括号里。如果项目规模稍大,引入 Druid 或 HikariCP 连接池,在 web.xml 或 context.xml 里配置 maxActive、maxWait 参数。期末作业用 DriverManager 可以,但要在答辩时能说出连接池的好处,这是加分项。

4.3 现象:成绩批量录入时部分成功部分失败,数据对不上

原因:没有开启事务,或者开启了但 catch 里忘了 rollback,又或者 rollback 之后没有 return,继续执行了 commit。还有一种情况是 MySQL 的表引擎是 MyISAM,它根本不支持事务,setAutoCommit(false) 形同虚设。

解决:先SHOW TABLE STATUS确认引擎是 InnoDB。然后在 Service 层显式控制事务,catch 里 rollback 并 return false,finally 里恢复状态。如果用了 Spring 的@Transactional,要注意自调用失效问题——同一个类里 A 方法调 B 方法,B 上的事务注解不生效,因为没走代理。期末作业手写事务反而更直观。

4.4 现象:删除班级时提示“Cannot delete or update a parent row”

原因:班级表被学生表的外键引用了,直接删班级会违反外键约束。这是数据库在保护数据一致性,不是 bug。

解决:两种策略。一是先检查该班级下有没有学生,有就提示用户先转班或删除学生;二是把外键的删除策略改成ON DELETE SET NULL或ON DELETE CASCADE,但要在业务上想清楚是否合理。学籍系统里通常不允许直接删班级,而是标记为“已毕业”状态,用软删除。这个设计点可以在答辩时展开讲,体现你对数据完整性的理解。

4.5 现象:JSP 页面修改后刷新浏览器没变化

原因:Tomcat 对 JSP 有编译缓存,或者 IDEA 的 Artifact 没有更新到部署目录。有时候是浏览器缓存了旧的 HTML。

解决:IDEA 里 Run → Edit Configurations → Server → 勾选 “On ‘Update’ action: Update classes and resources”,然后 Ctrl+F5 强制刷新浏览器。如果还不行,看 Tomcat 的 work 目录,删掉对应项目的编译缓存。更彻底的做法是部署时用 war exploded 模式并开启热部署,但热部署对 Servlet 类的修改有时不生效,改 Java 代码后还是建议重启。

5. 从能跑到能讲:给学籍管理系统加一个可复用的权限拦截器

5.1 用 Filter 做登录校验,别在每个 Servlet 里复制粘贴

期末作业的登录功能通常只做“用户名密码对了就跳转首页”,但首页的每个链接都没有校验,直接输 URL 就能进。这在演示时是个隐患,老师随手一试就发现没登录也能访问学生列表。正确的做法是加一个 Filter,拦截所有需要登录的路径,检查 session 里有没有用户对象。

// AuthFilter.java 放在 src/com/student/filter/ @WebFilter(urlPatterns = {"/student", "/score", "/class"}) public class AuthFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; HttpSession session = request.getSession(false); if (session == null || session.getAttribute("loginUser") == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } chain.doFilter(req, resp); // 已登录,放行 } }

逻辑说明:getSession(false)表示没有 session 就返回 null,不新建,避免给未登录用户创建无用 session。urlPatterns列出需要保护的 Servlet 路径,登录页和登录处理的 Servlet 不能包含在内,否则会死循环。request.getContextPath()拿到项目上下文路径,保证重定向地址正确。这个 Filter 只有十几行,但能让你的项目从“能跑”变成“像个正经系统”,答辩时老师问“没登录能不能直接访问”你可以当场演示拦截效果。

5.2 把 JDBC 连接参数抽到 properties 文件,换环境不用改代码

现在数据库密码是硬编码在 DBUtil 里的,换一台电脑演示就要改代码重新编译。更好的做法是放到src/db.properties,用类加载器读取。这样你交作业时可以把密码改成空或默认值,别人拿到压缩包改配置文件就能跑。

# src/db.properties jdbc.url=jdbc:mysql://localhost:3306/student_manager?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456 jdbc.driver=com.mysql.cj.jdbc.Driver
// DBUtil 读取配置的静态块 static { try (InputStream is = DBUtil.class.getClassLoader().getResourceAsStream("db.properties")) { Properties props = new Properties(); props.load(is); URL = props.getProperty("jdbc.url"); USER = props.getProperty("jdbc.username"); PASSWORD = props.getProperty("jdbc.password"); Class.forName(props.getProperty("jdbc.driver")); } catch (Exception e) { throw new RuntimeException("数据库配置加载失败", e); } }

这样改完之后,DBUtil 里不再出现任何明文密码,配置文件也可以被 .gitignore 排除,避免提交到公开仓库。如果你想把项目放到 GitHub 上当作品集,这一步是必须的。另外 properties 文件要放在 src 根目录下,编译后会在 classes 根目录,getClassLoader().getResourceAsStream才能找到。放在包里面的话路径要带包名,容易写错。

5.3 一个验证项目是否真正跑通的小技巧

不要只看首页能不能打开。按这个顺序点一遍:登录 → 学生列表(看分页)→ 搜索一个不存在的学号(看空结果处理)→ 添加一个学生(看中文是否乱码)→ 给该学生录两门成绩(看事务)→ 删除该学生(看成绩是否级联删除)→ 退出后直接访问 student?action=list(看是否被拦截)。七步走完没问题,这个项目才算真正能交付。我一般还会在添加学生时故意留空姓名,看后端有没有非空校验,没有的话补一个,这是最容易在演示时被挑出来的毛病。

最后说一个我自己的习惯:每次拿到别人的 JavaWeb 项目,先不改代码,而是把 web.xml 从头到尾读一遍,看有哪些 Servlet、哪些 Filter、哪些 listener,再顺着 URL 映射去读对应的类。这样半小时就能摸清整个项目的骨架,比从 JSP 开始翻快得多。学籍管理系统这类项目结构都很相似,你吃透一个,后面再遇到图书管理、人事管理、选课系统,基本就是换表名和字段的事。希望帮到你。

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

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

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

立即咨询