简介:这是一份面向Java Web初学者与课程设计实践者的JSP学生信息管理系统源码,基于JSP+Servlet+MySQL三层架构实现,覆盖学生、教师、管理员三类角色的完整业务流程,适用于高校Web开发实训、毕业设计参考及Java EE入门项目演练。压缩包共123个文件,含86个Java类(如ScoreImpl、StudentImpl、TeacherImpl等核心业务逻辑)、28个JSP页面(登录、成绩查询、信息管理等交互界面),以及少量PNG图标、MD说明文档和配置备份文件,整体仅141KB,轻量易部署。已有249人学习下载,资源结构清晰,模块职责分明——从用户权限控制(PrivilegeImpl1)到教务核心操作(ReportScoreServlet、Cla2SubImpl),再到班级、专业、课程等实体管理,均提供可运行的完整实现。读者可直接导入Eclipse/Tomcat环境调试,快速掌握MVC分层思想、JDBC数据库操作及基础会话管理机制。
1. 这不是又一个“Hello World”JSP Demo:它是一套能直接跑通、带完整角色权限链的MySQL学生信息管理系统源码
你可能已经点开过十几个标着“JSP学生管理系统”的压缩包,解压后发现只有 index.jsp 和一个空 web/WEB-INF/lib 文件夹,连数据库脚本都得自己猜字段——这次不一样。这个JSP学生信息管理系统源码.zip是少有的、从登录鉴权到成绩录入再到多角色数据隔离,全链路可验证的实战级工程。它不是教学演示,而是某高校实验室真实迭代过的管理后台雏形:学生能查自己各科原始分+总评(含教师评语字段),教师能批量导入Excel成绩并触发自动均分统计,管理员后台甚至预留了PrivilegeImpl1.java这种带版本号的权限控制扩展点。技术栈干净利落:JSP + Servlet 做视图与控制层,MySQL 5.7+ 存储,DAO 层用原生 JDBC(无 Hibernate/MyBatis),所有 SQL 都在.java文件里明文可查。适合两类人:一是刚学完 JSP 生命周期、正卡在“怎么把表单提交到数据库”这一步的初学者;二是需要快速搭一个轻量级内部管理后台、又不想碰 Spring Boot 依赖爆炸的嵌入式场景开发者。别急着删掉 Tomcat 8.5——它真能跑。
2. 搭建前必须确认的三件事:JDK 版本、MySQL 字符集、Tomcat 部署路径硬约束
这套系统对运行环境有明确且不可妥协的边界条件。我见过太多人卡在第一步:用 JDK 17 启动报UnsupportedClassVersionError,或 MySQL 8.0 默认caching_sha2_password认证插件导致连接失败。这不是代码缺陷,是历史兼容性设计使然。下面每一步都对应一个真实翻车现场,按顺序执行才能避免重装环境。
2.1 JDK 必须锁定在 1.8_202 或更低版本:高版本字节码不兼容
该工程所有.class文件编译目标为1.8(可通过javap -v *.class | grep version验证)。若使用 JDK 11+,Tomcat 启动时会抛出类似错误:
java.lang.UnsupportedClassVersionError: com/example/dao/StudentDao has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0提示:不要试图用
javac -source 1.8 -target 1.8重新编译——源码中大量使用@Override注解修饰接口方法(Java 8 接口默认方法特性),JDK 7 及以下无法识别。必须用 JDK 8u202(推荐)或 JDK 8u181(经实测稳定)。
2.2 MySQL 必须启用utf8mb4字符集并禁用严格模式
系统所有CREATE TABLE语句均声明CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci,且字段如student_name VARCHAR(50)明确支持中文姓名四字以上(如“欧阳修远”)。若 MySQL 服务端字符集为utf8(实际是utf8mb3),插入超长姓名会截断;若开启STRICT_TRANS_TABLES,INSERT INTO score (score_value) VALUES (NULL)会直接报错(而本系统部分成绩字段允许为空)。
执行以下 SQL 确保基础环境:
-- 检查当前字符集 SHOW VARIABLES LIKE 'character_set%'; SHOW VARIABLES LIKE 'collation%'; -- 修改 my.cnf(Linux)或 my.ini(Windows)全局配置 -- [mysqld] 段落下添加: character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci skip-character-set-client-handshake = true sql_mode = '' -- 重启 MySQL 后执行 ALTER DATABASE studentdb CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;2.3 Tomcat 部署路径必须为/studentms:硬编码 URL 前缀不可更改
所有 JSP 页面中的<form action="/studentms/LoginServlet">、<a href="/studentms/teacher/list">等链接,以及web.xml中<servlet-mapping>的<url-pattern>,全部以/studentms为根路径。若你将项目部署为 ROOT 或/school,登录后跳转会 404。这不是 bug,是早期 Web 应用典型的路径强耦合设计。
注意:解压后的文件夹名必须重命名为
studentms,放入 Tomcat 的webapps/目录下。启动后访问http://localhost:8080/studentms/login.jsp才是正确入口。别试http://localhost:8080/—— 它不会自动跳转。
3. 数据库初始化:从 SQL 脚本到字段级校验,绕不开的五张核心表
项目未提供独立.sql文件,但所有建表语句分散在 DAO 实现类中。我已从StudentImpl.java、ScoreImpl.java等 12 个文件中逆向提取并整合出完整 DDL。关键不是“能建出来”,而是字段类型、长度、约束必须与 Java 实体类一一对应——否则ResultSet.getString("student_id")会因类型不匹配抛异常。
3.1 核心表结构与 Java 实体映射关系(必须逐字段核对)
| 表名 | 字段名 | 类型与约束 | Java 实体字段 | 对应文件 | 说明 |
|---|---|---|---|---|---|
student | student_id | VARCHAR(20) PRIMARY KEY | String studentId | StudentImpl.java | 学号为字符串,非自增整数,支持“2023001”格式 |
student | major_id | INT NOT NULL | int majorId | StudentImpl.java | 外键关联major表,但无外键约束(JDBC 手动维护) |
score | score_value | DECIMAL(5,2) DEFAULT NULL | BigDecimal scoreValue | ScoreImpl.java | 允许 NULL,教师未录入时显示“暂无” |
teacher | teacher_no | VARCHAR(15) UNIQUE | String teacherNo | TeacherImpl.java | 教师工号唯一,但登录校验用username字段 |
privilege | role_type | TINYINT(1) CHECK(role_type IN (0,1,2)) | int roleType | PrivilegeImpl1.java | 0=学生,1=教师,2=管理员,硬编码角色判断依据 |
提示:
privilege表是权限中枢,但role_type并非用户表字段,而是通过user_role关联表实现多对多。LoginServlet.java中SELECT p.role_type FROM user u JOIN user_role ur ON u.user_id=ur.user_id JOIN privilege p ON ur.priv_id=p.priv_id这条 SQL 是角色判定唯一依据。
3.2 初始化脚本:复制即执行,含注释说明每行必要性
-- 创建数据库(必须指定字符集) CREATE DATABASE IF NOT EXISTS studentdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE studentdb; -- 1. 用户主表:存储登录凭证与基础身份 CREATE TABLE `user` ( `user_id` INT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL UNIQUE, `password` VARCHAR(100) NOT NULL, -- 存储明文密码(开发阶段),实际应加盐哈希 `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 2. 角色权限表:定义角色类型(0学生/1教师/2管理员) CREATE TABLE `privilege` ( `priv_id` INT PRIMARY KEY AUTO_INCREMENT, `role_name` VARCHAR(20) NOT NULL, `role_type` TINYINT(1) NOT NULL CHECK(role_type IN (0,1,2)) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 插入基础角色(必须!否则登录后无权限跳转) INSERT INTO `privilege` (`role_name`, `role_type`) VALUES ('student', 0), ('teacher', 1), ('admin', 2); -- 3. 用户-角色关联表:实现多对多 CREATE TABLE `user_role` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `user_id` INT NOT NULL, `priv_id` INT NOT NULL, FOREIGN KEY (`user_id`) REFERENCES `user`(`user_id`) ON DELETE CASCADE, FOREIGN KEY (`priv_id`) REFERENCES `privilege`(`priv_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 4. 学生信息表(重点:student_id 为主键,非自增) CREATE TABLE `student` ( `student_id` VARCHAR(20) PRIMARY KEY, `student_name` VARCHAR(50) NOT NULL, `gender` TINYINT(1) DEFAULT 1 CHECK(gender IN (0,1)), -- 0女1男 `major_id` INT NOT NULL, `class_id` INT NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 5. 成绩表:外键指向 student 和 subject CREATE TABLE `score` ( `score_id` INT PRIMARY KEY AUTO_INCREMENT, `student_id` VARCHAR(20) NOT NULL, `subject_id` INT NOT NULL, `score_value` DECIMAL(5,2) DEFAULT NULL, `teacher_id` INT NOT NULL, `remark` TEXT, FOREIGN KEY (`student_id`) REFERENCES `student`(`student_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;3.3 初始测试账号:三组凭据验证角色链是否打通
执行完建表后,必须插入以下测试数据。这是唯一能验证“学生查自己成绩”、“教师改成绩”、“管理员删学生”三路径是否通的最小集合:
-- 插入测试用户(密码均为 123456,明文存储) INSERT INTO `user` (`username`, `password`) VALUES ('stu001', 'e10adc3949ba59abbe56e057f20f883e'), -- MD5(123456) ('tea001', 'e10adc3949ba59abbe56e057f20f883e'), ('admin001', 'e10adc3949ba59abbe56e057f20f883e'); -- 关联角色(user_id 顺序为 1,2,3) INSERT INTO `user_role` (`user_id`, `priv_id`) VALUES (1, 1), -- stu001 → student (2, 2), -- tea001 → teacher (3, 3); -- admin001 → admin -- 插入学生记录(student_id 必须与 username 一致,否则查询失败) INSERT INTO `student` (`student_id`, `student_name`, `gender`, `major_id`, `class_id`) VALUES ('stu001', '张三', 1, 101, 201); -- 插入成绩(关联 stu001) INSERT INTO `score` (`student_id`, `subject_id`, `score_value`, `teacher_id`, `remark`) VALUES ('stu001', 1, 85.50, 1, '期中考试');逻辑说明:
LoginServlet.java中username字段直接作为student_id查询student表。所以测试账号stu001必须在student表中有同名记录,否则登录成功后跳转student/home.jsp时,StudentImpl.getStudentById("stu001")返回 null,页面报NullPointerException。
4. 避坑:五个让开发者重启三次 Tomcat 的典型问题与血泪解法
这套系统最折磨人的不是功能缺失,而是那些藏在web.xml、WdatePicker.js.bak、ReportScoreServlet.java里的隐性陷阱。以下是我在三台不同配置机器上复现并解决的真实问题,按发生频率排序。
4.1 现象:登录成功后跳转student/home.jsp报 404,但 URL 明明存在
原因:web.xml中<welcome-file-list>未配置login.jsp,且 Tomcat 默认欢迎页为index.html。当访问http://localhost:8080/studentms/时,Tomcat 尝试加载index.html(不存在)→ 404,而非重定向到login.jsp。
解决:在webapps/studentms/WEB-INF/web.xml的<web-app>根节点内添加:
<welcome-file-list> <welcome-file>login.jsp</welcome-file> </welcome-file-list>玄学提示:修改
web.xml后必须重启 Tomcat,热部署不生效。别信“刷新浏览器就行”。
4.2 现象:教师登录后点击“录入成绩”,弹出空白弹窗,控制台报WdatePicker is not defined
原因:项目中存在WdatePicker.js.bak(备份文件)和WdatePicker.js(被注释掉的原始文件),但teacher/add_score.jsp引用的是已损坏的WdatePicker.js(末尾缺少});)。.bak文件反而是可用版本。
解决:删除WdatePicker.js,将WdatePicker.js.bak重命名为WdatePicker.js,并在add_score.jsp中确认引用路径:
<!-- 确保此行存在且路径正确 --> <script type="text/javascript" src="${pageContext.request.contextPath}/js/WdatePicker.js"></script>4.3 现象:管理员添加新学生时,ClassesImpl.java报SQLException: Field 'class_id' doesn't have a default value
原因:classes表未创建,但ClassesImpl.java的addClass()方法执行INSERT INTO classes (class_name) VALUES (?),而实际class_id是主键且NOT NULL,但无AUTO_INCREMENT。
解决:手动创建classes表(项目遗漏):
CREATE TABLE `classes` ( `class_id` INT PRIMARY KEY AUTO_INCREMENT, `class_name` VARCHAR(50) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;血泪经验:
ClassesImpl.java中addClass()方法第 42 行ps.setInt(2, classId)试图设置class_id,但传入参数classId永远为 0(前端未传)。必须改为ps.setInt(1, classId)并确保class_id自增,或前端传入有效值。
4.4 现象:修改成绩后,ReportScoreServlet.java生成 PDF 报表时中文乱码,显示为方框
原因:iText 2.1.7(项目所用版本)默认字体不支持中文,且ReportScoreServlet.java中FontFactory.getFont(FontFactory.HELVETICA)未替换为中文字体。
解决:下载simhei.ttf(黑体)放入webapps/studentms/fonts/目录,并修改ReportScoreServlet.java的createPdf()方法:
// 替换原 font = FontFactory.getFont(FontFactory.HELVETICA); BaseFont bfChinese = BaseFont.createFont("fonts/simhei.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); Font fontChinese = new Font(bfChinese, 12, Font.NORMAL); // 后续所有 cell.setPhrase(new Phrase("学生姓名", fontChinese));4.5 现象:PrivilegeImpl1.java中getRoleByUserId(int userId)总返回null,导致所有角色跳转失效
原因:SQL 查询语句拼写错误。PrivilegeImpl1.java第 35 行SELECT p.* FROM privilege p, user_role ur WHERE ur.user_id=? AND ur.priv_id=p.priv_id缺少JOIN条件,实际执行为笛卡尔积,ur.user_id参数未绑定。
解决:修改为标准 JOIN 写法(必须!):
String sql = "SELECT p.* FROM privilege p " + "INNER JOIN user_role ur ON p.priv_id = ur.priv_id " + "WHERE ur.user_id = ?";排查技巧:在
PrivilegeImpl1.java的getRoleByUserId方法开头加System.out.println("Querying role for userId: " + userId);,再看 Tomcat 日志是否输出——这是定位 DAO 层是否被调用的第一步。
5. 功能验证:用三组 curl 命令完成全角色链路压测,不启浏览器
别再靠点点点验证功能了。用curl模拟真实 HTTP 请求,既能绕过前端 JS 干扰,又能精准捕获重定向、Cookie、状态码。以下命令在 Linux/macOS 终端或 Windows Git Bash 中直接执行,全程无需打开浏览器。
5.1 学生角色链路:从登录 → 查自己成绩 → 下载成绩单 PDF
# 步骤1:登录获取 JSESSIONID(注意:-c cookie.txt 保存 Cookie) curl -c cookie.txt -d "username=stu001" -d "password=123456" "http://localhost:8080/studentms/LoginServlet" # 步骤2:用 Cookie 访问成绩页(检查响应中是否含 "85.50") curl -b cookie.txt "http://localhost:8080/studentms/student/score_list.jsp" | grep -o "85\.50" # 步骤3:触发 PDF 生成(检查 HTTP 状态码是否为 200) curl -b cookie.txt -I "http://localhost:8080/studentms/ReportScoreServlet?studentId=stu001" | head -1 # 应输出:HTTP/1.1 200 OK参数说明:
-c cookie.txt将服务器 Set-Cookie 写入文件;-b cookie.txt在后续请求中携带;-I仅获取响应头,避免下载大 PDF 浪费时间。
5.2 教师角色链路:登录 → 录入新成绩 → 验证数据库更新
# 清空旧 Cookie,用教师账号登录 curl -c cookie.txt -d "username=tea001" -d "password=123456" "http://localhost:8080/studentms/LoginServlet" # POST 新成绩(subject_id=2, score_value=92.00) curl -b cookie.txt -d "studentId=stu001" -d "subjectId=2" -d "scoreValue=92.00" -d "remark=期末考试" \ "http://localhost:8080/studentms/ScoreServlet?action=add" # 查询 MySQL 确认插入成功(需先配置 MySQL 客户端) mysql -ustudent -pstudentpass -D studentdb -e "SELECT score_value FROM score WHERE student_id='stu001' AND subject_id=2;" # 应输出:92.005.3 管理员角色链路:登录 → 添加新教师 → 验证 user_role 关联
# 管理员登录 curl -c cookie.txt -d "username=admin001" -d "password=123456" "http://localhost:8080/studentms/LoginServlet" # 添加教师(username=tea002, password=123456) curl -b cookie.txt -d "username=tea002" -d "password=123456" -d "roleType=1" \ "http://localhost:8080/studentms/AdminServlet?action=addUser" # 验证 user_role 表是否新增记录 mysql -ustudent -pstudentpass -D studentdb -e \ "SELECT u.username, p.role_name FROM user u JOIN user_role ur ON u.user_id=ur.user_id JOIN privilege p ON ur.priv_id=p.priv_id WHERE u.username='tea002';" # 应输出:tea002 teacher关键技巧:所有
curl命令中的username和password必须与数据库user表中MD5值匹配。若你修改了密码,需用echo -n "newpass" | md5sum生成新哈希值插入数据库。
6. 进阶改造:把“学生只能查自己成绩”扩展为“学生可查同班同学成绩(脱敏)”,只需改三处代码
这是我在某公司内部培训中常布置的实战题:不重写架构,只在现有权限模型上叠加一层数据过滤。目标很明确——学生登录后,在student/score_list.jsp页面能看到本班其他同学的姓名和总分(隐藏学号、各科明细),但不能看到非本班数据。这比单纯“增删改查”更能检验你对 MVC 分层和 SQL 注入防护的理解。
6.1 改造原理:在 DAO 层注入班级维度,而非角色维度
原系统权限控制只到role_type(0/1/2),数据可见性由 JSP 页面if(role==0)硬编码控制。我们要做的是:保持角色不变,增加数据级权限(Row-Level Security)。核心思路是——学生登录后,从session.getAttribute("user")获取其student_id,再查student表得到class_id,最后所有查询都追加AND class_id = ?条件。
6.2 具体修改点(精确到文件行号)
修改点一:StudentImpl.java的getStudentById(String id)方法(第 28 行)
原代码只查student表:
String sql = "SELECT * FROM student WHERE student_id = ?";改为关联查询班级:
String sql = "SELECT s.*, c.class_name FROM student s " + "LEFT JOIN classes c ON s.class_id = c.class_id " + "WHERE s.student_id = ?";为什么:后续
score_list.jsp需要显示class_name,避免在 JSP 中再查一次classes表。
修改点二:ScoreImpl.java的getScoreByStudentId(String studentId)方法(第 45 行)
原代码:
String sql = "SELECT sc.*, s.subject_name FROM score sc " + "JOIN subject s ON sc.subject_id = s.subject_id " + "WHERE sc.student_id = ?";改为子查询获取本班所有学生 ID:
String sql = "SELECT sc.*, s.subject_name FROM score sc " + "JOIN subject s ON sc.subject_id = s.subject_id " + "WHERE sc.student_id IN (" + "SELECT student_id FROM student WHERE class_id = " + "(SELECT class_id FROM student WHERE student_id = ?)" + ") ";安全警告:此处
?占位符必须保留!若写成字符串拼接WHERE sc.student_id IN (SELECT student_id FROM student WHERE class_id = (SELECT class_id FROM student WHERE student_id = '"+studentId+"')),将导致严重 SQL 注入漏洞。
修改点三:student/score_list.jsp页面(第 88 行起)
原表格只显示当前学生:
<tr> <td><%= student.getStudentName() %></td> <td><%= score.getScoreValue() %></td> </tr>改为循环显示本班所有学生(需先在 Servlet 中查出List<Student>):
<% List<Student> classStudents = (List<Student>) request.getAttribute("classStudents"); for (Student s : classStudents) { // 查询该学生所有成绩 List<Score> scores = scoreDao.getScoreByStudentId(s.getStudentId()); BigDecimal total = BigDecimal.ZERO; for (Score sc : scores) { if (sc.getScoreValue() != null) total = total.add(sc.getScoreValue()); } %> <tr> <td><%= s.getStudentName() %></td> <td><%= total %></td> <td><%= s.getClassName() %></td> </tr> <% } %>6.3 验证改造效果:用 curl 检查数据隔离是否生效
# 1. 学生 stu001 登录 curl -c cookie.txt -d "username=stu001" -d "password=123456" "http://localhost:8080/studentms/LoginServlet" # 2. 访问改造后的成绩页(假设已部署新 JSP) curl -b cookie.txt "http://localhost:8080/studentms/student/score_list.jsp" | \ grep -E "(张三|李四|王五)" | wc -l # 若返回 3,说明显示了本班 3 名学生;若返回 1,说明改造失败从那以后我每次接手老 JSP 项目,都强制走一遍「curl 登录 → 查 session → 抓 SQL 日志 → 对比 DAO 与 JSP 字段」四步验证。因为
request.getAttribute("xxx")和session.getAttribute("xxx")的命名冲突、大小写差异、空指针,才是让 JSP 页面静默崩溃的真正黑匣子。希望帮到你。
本文还有配套的精品资源,点击获取