
简介面向JavaWeb课程设计与期末大作业场景这份学生信息管理系统项目包支持直接部署使用系统覆盖学生信息维护、成绩录入与查询、课程管理等典型业务模块界面简洁、操作流畅代码注释清晰适合Java初学者及需要快速完成课设作业的在校生。资源共155个文件类型涵盖Java源码与编译后的class文件、JSP页面、XML配置、SQL数据库脚本以及Jar依赖包等其中SQL脚本用于快速初始化数据库JSP与Java文件方便对照学习前后端数据交互压缩包整体仅9.48MB下载与部署都非常轻量。目前已有542人学习下载说明这套方案经过较多同类场景验证。配套详细文档说明从环境配置、数据库设计到功能实现均有梳理可大幅节省从零搭建系统的时间适合作为高分课程设计或期末大作业的完整参考方案。1. JavaWeb 学生信息管理系统为什么它是课程设计稳妥选题期末前两周很多学生手里只有一份需求文档连“新建 JavaWeb 项目”都还在踩坑。学生信息管理系统在这个时间点反而是一个务实选择它不像电商系统要处理购物车和支付也不像博客系统要纠结富文本编辑但它的功能面又足够宽——学生、课程、成绩、密码修改四类业务刚好把 Servlet、JSP、JDBC、MySQL 建表和增删改查全部串起来。这套 JavaWeb 学生信息管理系统本质上就是 JSP Servlet 的经典写法压缩包里的源码、数据库脚本和文档说明配合起来是一个适合边抄边理解的 JavaWeb 项目完整案例。下面按我看代码、配环境、跑通业务、准备演示的顺序展开。2. 从 Action 类看清分层学生信息管理系统的请求入口与 MVC 边界2.1 Action 后缀的类不是 Struts是作者命名的 Servlet刚开始翻这份源码时我习惯性地找 struts.xml因为ScoreManageAction、ModifyPasswordAction这类命名太像 Struts2 里的 Action。实际把web.xml和类上的WebServlet注解看完之后会发现项目并没有引入 SSH 那套框架所谓 Action 只是作者给HttpServlet子类起的名字每个 Action 负责一个相对独立的请求入口。这样命名的好处是类名就是业务动作列表页、表单页和后台处理逻辑能快速对应答辩被问到“这个请求去哪了”时不需要翻完整个 servlet 包。类名典型请求路径主要作用返回方式AddStudent/addStudent接收新增学生表单成功后重定向到学生列表ModifyStuInfoAction/modifyStuInfo修改学生基本信息成功重定向失败回编辑页ModifyStudentAction/modifyStudent按学号定位学生后更新转发到列表或回显错误ListStudent/listStudent查询学生列表转发到 listStudent.jspAddSubjectAction/addSubject新增课程重定向到课程管理ModifySubjectAction/modifySubject修改课程名称等信息重定向到课程管理AddScoreAction/addScore录入或更新成绩重定向到成绩管理ScoreManageAction/scoreManage成绩列表入口转发到成绩页GetScoreAction/getScore按条件查询成绩转发到查询结果页ModifyPasswordAction/modifyPassword修改当前登录人密码成功重定向到登录页这张表不是web.xml的原样截图而是我按类名和常见打包方式整理出来的路由对照下载后如果类上写了WebServlet(/xxx)以源码注解为准。入口理解清楚之后底下的请求链就不难推了。2.2 请求从 JSP 表单到 MySQL 的完整路径任意一条新增请求按照 JSP → Servlet → DAO → MySQL → JSP 的顺序执行。下面用AddStudent的doPost作为例子protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String stuNo request.getParameter(stuNo); String stuName request.getParameter(stuName); String gender request.getParameter(gender); Student student new Student(); student.setStuNo(stuNo); student.setStuName(stuName); student.setGender(gender); StudentDao dao new StudentDao(); int rows dao.insert(student); if (rows 1) { response.sendRedirect(request.getContextPath() /listStudent); } else { request.setAttribute(error, 学号已存在或数据不完整); request.getRequestDispatcher(/addStudent.jsp).forward(request, response); } }这个过程有三个容易看漏的地方。第一request.setCharacterEncoding(UTF-8)必须放在getParameter之前否则 Tomcat 默认用 ISO-8859-1 读请求体中文参数在入口就变成乱码后面 JSP 无论怎么改都补不回来。第二成功路径用sendRedirect失败路径用forward两者不能混着用。sendRedirect发的是 302 响应浏览器会再发一次新请求原来的 request 域已经销毁所以“先 setAttribute 再 redirect”在目标页取不到值要传提示信息只能放在 URL 参数或者 session 里。第三sendRedirect里带了request.getContextPath()这是避免项目部署在http://localhost:8080/student_manage这种带上下文路径的地址时跳到根路径去。2.3 DAO 里的 SQL 为什么必须用 PreparedStatementAddStudent拿到参数后不会自己拼 SQL而是交给StudentDao。课程设计资源里常见的写法是把 SQL 模板、参数设置和结果处理分成三个动作public int insert(Student stu) { String sql INSERT INTO student (stu_no, stu_name, gender) VALUES (?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, stu.getStuNo()); ps.setString(2, stu.getStuName()); ps.setString(3, stu.getGender()); return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } }PreparedStatement有两个作用。一是把参数和 SQL 模板分开字符串值里的引号、单引号不会被当成 SQL 结构执行课程设计阶段不会出现“输入1 OR 11把整张表带出来”的硬伤。二是setString、setDate这类方法会主动做类型转换学号、课程编号在 MySQL 里是 VARCHAR在 Java 里是 String天然匹配。这里还用了 Java 7 之后的 try-with-resourcesConnection、PreparedStatement不用手动 close比在 finally 里塞三个 close 清爽很多。如果源码里是旧版写法也没必要急着改能跑通就保持原样答辩时能说出 PreparedStatement 和 Statement 的差别就够了。3. JavaWeb 配置与数据库初始化从连接参数到 Tomcat 部署配环境是这门课里最容易耗掉一整晚的环节问题多半不在 Java 代码而在版本和参数。3.1 JDK、Tomcat、MySQL 的版本矩阵开发机 JDKTomcatMySQLJDBC 驱动适用场景JDK 8Tomcat 8.5MySQL 5.7mysql-connector-java 5.x老课程设计项目最常见JDK 8Tomcat 9MySQL 8.0mysql-connector-java 8.x需要配 serverTimezoneJDK 11Tomcat 10MySQL 8.0mysql-connector-java 8.x包名从 javax.servlet 换成 jakarta.servlet我的建议是优先选 JDK 8 Tomcat 8.5 MySQL 5.7 的组合。不是因为旧而是课程设计资源多数是按这个组合写的驱动路径、Servlet 包名、数据库驱动都不用改。如果你下载的源码里 import 的是javax.servlet.http.HttpServlet就不要直接丢到 Tomcat 10 上运行Tomcat 10 已经把 API 迁移到jakarta.servlet包启动时会报ClassNotFoundException: javax.servlet.http.HttpServlet。这一句对很多学生来说是隐藏坑文档里不一定写。3.2 建表脚本里的字段关系如果压缩包里的.sql文件已经写好直接导入最省事。为了把字段关系讲清楚我复现时用的简化版本是这样CREATE DATABASE IF NOT EXISTS student_manage DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE student_manage; CREATE TABLE t_user ( username VARCHAR(30) PRIMARY KEY, password VARCHAR(64) NOT NULL, role VARCHAR(20) DEFAULT admin ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE student ( stu_no VARCHAR(20) PRIMARY KEY, stu_name VARCHAR(30) NOT NULL, gender CHAR(2) DEFAULT 男, enter_date DATE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE subject ( subject_no VARCHAR(20) PRIMARY KEY, subject_name VARCHAR(50) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE score ( id INT AUTO_INCREMENT PRIMARY KEY, stu_no VARCHAR(20) NOT NULL, subject_no VARCHAR(20) NOT NULL, score DECIMAL(5, 1), UNIQUE KEY uk_stu_sub (stu_no, subject_no), CONSTRAINT fk_score_student FOREIGN KEY (stu_no) REFERENCES student (stu_no), CONSTRAINT fk_score_subject FOREIGN KEY (subject_no) REFERENCES subject (subject_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; INSERT INTO t_user (username, password) VALUES (admin, 123456);这里有两个设计点值得在课程设计文档里写清楚。score表没有直接用学号当主键而是放了一个自增的id再对(stu_no, subject_no)建唯一键以后的扩展空间更大。比如同一个学生重复提交某门课程成绩时同一个学号加课程编号第二次插入会被唯一键挡住后续可以做“成绩更新”而不是“重复插入”。外键约束保证了成绩表里不会出现一个不存在的学生课程设计阶段用外键没有性能压力反而让数据关系清楚。utf8mb4不是选错了这是 MySQL 8 下支持中文和 emoji 的正确字符集。如果资源里的建表脚本写的是utf8也能导入只是遇到特别生僻的汉字时可能显示成问号。3.3 db.properties 参数与三个高频出错点连接参数一般都写在src下的资源配置文件里常见命名是db.properties或jdbc.propertiesjdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/student_manage?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password123456参数作用什么时候必须加useUnicodetruecharacterEncodingutf8让 JDBC 以 UTF-8 与数据库交换数据中文乱码时重点检查useSSLfalse关闭 SSL 握手减少开发环境告警MySQL 8 常见MySQL 5.7 可不加serverTimezoneAsia/Shanghai指定数据库时区MySQL 8 不加可能报时区错误allowPublicKeyRetrievaltrue允许客户端获取 RSA 公钥使用 caching_sha2_password 时可能用到最常见的三个错误是驱动类写成了com.mysql.jdbc.Driver在 MySQL 8 下这个旧类名已经不存在password里带了空格却看不出来URL 里3306/库名的库名和实际建库名大小写不一致导致Unknown database。Linux 下 MySQL 的数据库名、表名是区分大小写的所以代码里查student建表就不要建Student。3.4 Tomcat 启动和端口占用处理Windows 下解压 Tomcat 后直接运行bin/startup.batLinux 或 Mac 用cd /opt/tomcat/bin ./startup.sh tail -20f ../logs/catalina.out如果浏览器访问http://localhost:8080/student_manage/打不开先做两件事。一是确认 Tomcat 是否真的启动成功看catalina.out最后有没有Server startup in [xxx] milliseconds二是确认 8080 端口是否被占用Windows 命令netstat -ano | findstr 8080Linux 命令netstat -ano | grep 8080。端口被占时改conf/server.xml里的 Connector 端口Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 /改完重启 Tomcat访问地址也要跟着改成新端口。注意上下文路径通常情况下项目 war 包放在webapps下会生成http://localhost:8080/项目名/如果只写http://localhost:8080/会回到 Tomcat 初始页面然后以为项目没部署。4. 学生、课程、成绩、密码四个业务的代码级拆解4.1 ListStudent 的列表查询与转发ListStudent作为列表入口通常用doGet而不是doPost因为列表请求是查询不是提交。一个标准的列表查询会把 DAO 的结果放到 request 域里再通过forward交给 JSP 渲染protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String keyword request.getParameter(keyword); StudentDao dao new StudentDao(); ListStudent list keyword null || keyword.trim().isEmpty() ? dao.findAll() : dao.findByKeyword(keyword.trim()); request.setAttribute(studentList, list); request.getRequestDispatcher(/listStudent.jsp).forward(request, response); }注意dao.findAll()和dao.findByKeyword()返回的都是ListStudentJSP 页面上用c:forEach遍历时不需要关心底层是 ArrayList 还是其他实现。若keyword为空字符串直接传给 SQL 会导致WHERE stu_name LIKE %%虽然结果正确但没有任何过滤意义因此先做判空再决定走哪个方法。4.2 AddStudent、ModifyStuInfoAction 的参数校验学号、姓名这类字段不能只依赖前端必填提示因为请求完全可以绕过页面直接发到 Servlet。一个简单有效的做法是进入 DAO 之前做正则校验if (stuNo null || !stuNo.matches([A-Za-z0-9]{6,20})) { request.setAttribute(error, 学号只能是 6-20 位字母或数字); request.getRequestDispatcher(/addStudent.jsp).forward(request, response); return; }ModifyStuInfoAction和ModifyStudentAction同时存在时务必分清各自的使用场景。课程设计里经常出现一个是管理员编辑学生资料一个是按学号查询后修改字段接口路径不同、参数不同。写代码时不要因为逻辑相似就共用同一个 DAO 方法答辩现场老师很可能问“这两个类有什么区别”你没有心理准备就会被问住。正常情况下修改操作后的返回方式和新增一致成功后重定向到列表页失败后转发回编辑页并把原来的值回显出来。4.3 AddScoreAction、GetScoreAction 的成绩写入与联表查询成绩录入是评分表最看重的功能。score表有了(stu_no, subject_no)唯一键之后录入接口通常会写成“存在就更新不存在就插入”String sql INSERT INTO score (stu_no, subject_no, score) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE score ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, stuNo); ps.setString(2, subjectNo); ps.setBigDecimal(3, score); ps.setBigDecimal(4, score); ps.executeUpdate(); }这里重复传了两次score第一次是插入值第二次是冲突后的更新值。ON DUPLICATE KEY UPDATE依赖 MySQL 的唯一键冲突如果表结构里没有唯一键这个写法不会报错但也不会触发更新等于多次插入重复数据。因此建表时UNIQUE KEY uk_stu_sub (stu_no, subject_no)是不能省的。GetScoreAction负责查询成绩单查一张score表只能看到学号和课程号页面展示时通常要关联学生姓名和课程名称SELECT s.stu_no, s.stu_name, su.subject_name, sc.score FROM score sc JOIN student s ON sc.stu_no s.stu_no JOIN subject su ON sc.subject_no su.subject_no WHERE sc.stu_no ? ORDER BY sc.score DESC在 DAO 里执行这段 SQL 后ResultSet中的stu_name和subject_name属于联表字段不要在 Student 实体里硬塞这两个属性更自然的做法是建一个ScoreVO把成绩查询页需要展示的字段单独放进去。课程设计阶段可能不会严格要求分层但 VO 和实体区分开写文档会顺手很多。4.4 AddSubjectAction、ModifySubjectAction、ModifyPasswordAction 的注意点课程管理功能相对独立AddSubjectAction只需要接收subjectNo和subjectName两个参数插入前检查课程编号是否已存在。重复课程在subject表主键有冲突约束所以 DAO 捕获SQLIntegrityConstraintViolationException后返回失败信息即可不需要再写一次 select 判断。ModifyPasswordAction是容易被忽略的安全点。修改密码前必须确认当前用户是登录状态并且旧密码匹配String username (String) session.getAttribute(username); User user userDao.findByUsername(username); if (user ! null user.getPassword().equals(oldPwd)) { userDao.updatePassword(username, newPwd); session.invalidate(); response.sendRedirect(request.getContextPath() /login.jsp); } else { request.setAttribute(error, 原密码不正确); request.getRequestDispatcher(/modifyPassword.jsp).forward(request, response); }这里有两个细节。第一username不能从表单获取只能从 session 取否则任何知道当前页面地址的人都能改成别人的密码。第二改完后执行session.invalidate()让用户重新登录目的是让旧的会话失效避免密码改了但原 token 还能继续操作。课程设计不要求做多复杂的安全体系但这两行代码能明显看出你考虑过会话安全问题。4.5 运行中常见的三个异常与排查报错现象可能原因排查方式ClassNotFoundException: com.mysql.jdbc.Driver驱动 jar 没放在 WEB-INF/lib或驱动名写旧检查 lib 目录改成 com.mysql.cj.jdbc.DriverTable student_manage.student doesnt exist表名大小写不一致或建表脚本没执行成功在 MySQL 里执行 SHOW TABLES 确认NumberFormatException: For input string: 把空字符串用 Integer.parseInt 解析在 DAO 前校验参数空值直接 return遇到任何异常先看catalina.out或 IDE 控制台的堆栈第一行不要被一长串 Caused by 带偏。SQLIntegrityConstraintViolationException说明是数据约束冲突NullPointerException说明对象为空优先查 Servlet 里getParameter的参数名是否和 JSP 表单的 name 对得上。5. 答辩演示路径与验证把“能跑”变成“老师看得懂”5.1 固定演示链路避免现场临时找数据答辩现场最怕的是点开菜单发现没有数据临时录入又输错字段。我一般会把演示路径固定成一条链路并且提前在数据库里准备好两组数据用admin/123456登录进入系统主页面。进入学生管理点击新增填写一个S001/张三/男。进入课程管理新增C001/JAVA 程序设计。进入成绩管理给 S001 录 C001 的成绩 88。返回成绩列表确认列表页显示的是“张三”而不是学号。再用关键词“张”查询确认 ListStudent 的模糊查询生效。修改密码改完确认自动退出登录再改回原密码。演示完这七步再回到数据库做最后一个验证SELECT s.stu_no, s.stu_name, su.subject_name, sc.score FROM score sc JOIN student s ON sc.stu_no s.stu_no JOIN subject su ON sc.subject_no su.subject_no WHERE sc.stu_no S001;这条 SQL 跑出来的结果如果和应用页面上显示的内容一致说明 DAO 层的联表查询、JSP 层的渲染、数据库表数据三者是对得上的。老师问“成绩存在哪张表”的时候直接把查询结果指给老师看。5.2 提交前花 10 秒确认字符集课程设计最常见的“减分项”不是功能缺失而是页面上出现中文乱码、URL 里带中文、Tomcat 控制台输出问号。与其到答辩前一晚到处搜解决方案不如在部署阶段先确认数据库字符集SHOW VARIABLES LIKE character_set_database; SHOW VARIABLES LIKE collation_database;第一行返回utf8mb4第二行返回utf8mb4_general_ci或utf8mb4_unicode_ci基本可以排除数据库这一侧的原因。再确认 Servlet 里request.setCharacterEncoding(UTF-8)写在所有getParameter之前JSP 页面顶部contentTypetext/html; charsetUTF-8没有漏掉乱码问题就解决了大半。最后建议把所有页面请求统一成“.do或/xxx”的路径风格不要一个项目里 JSP 直接跳、Servlet 又 redirect答辩时老师顺着地址栏看项目结构会很顺畅。这一步不会给功能加分但会让整个 JavaWeb 课程设计看起来更像一个规范交付的项目而不是临时拼出来的脚本。本文还有配套的精品资源点击获取