ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Java Web学生成绩管理系统实战:Servlet、JSP与JDBC核心机制拆解

Java Web学生成绩管理系统实战:Servlet、JSP与JDBC核心机制拆解 简介基于Java技术栈的学生成绩管理系统项目整合Servlet、JSP、Bootstrap与MySQL适用于Java Web初学者、在校学生及毕业设计人员可解决成绩数据存储、管理与查询效率低下的问题。系统覆盖学生信息管理、课程成绩录入与修改、按姓名/课程/学期多条件查询、平均分及及格率统计、数据备份与恢复等完整功能并采用MVC设计模式将业务逻辑、数据访问与页面展示清晰分层。压缩包共575个文件9.17MB主要包含85个Java源码、74个JSP页面、170个编译后的class文件以及JS/CSS/HTML等前端资源、SQL数据库脚本和jar运行依赖库其中class为编译产物、java为源码、jsp为动态页面、sql为初始化脚本目录结构便于定位。已有3117人学习下载。项目从Servlet请求处理、JSP动态页面渲染到Bootstrap响应式布局、MySQL数据库操作均有可运行代码完整展示了Java Web开发全流程可直接作为课程设计或毕业设计的参考模板也是巩固Servlet/JSP基础与MVC实践的实用素材。1. 这套学生成绩管理系统为什么值得照着拆一遍前几天帮一位读者排查他课程设计里的问题他拿着一个打着 SSM 标签的项目问我为什么启动报 500我打开一看Spring、SpringMVC、MyBatis 全配了但连个最基本的成绩查询都跑不通。相比之下这套基于 Java Servlet JSP Bootstrap MySQL 的学生成绩管理系统反而清爽得多结构清晰、依赖极少、跑起来不玄学。它不是什么高深框架但它把 Java Web 最核心的那套东西——请求怎么进来、数据怎么存取、页面怎么渲染——全串起来了。适合正在做课程设计、毕业设计的人也适合准备 Java 面试、想把 Servlet 生命周期弄清楚的人。先说结论这个系统源码能拿到手照着跑一遍你对 Java Web 的整体认知会比刷十篇博客都扎实。2. 先看骨架项目结构和 Servlet 的请求流转2.1 目录结构与 MVC 分层逻辑这个项目从类名上就能看出典型的 MVC 分层思路ScoreDaoImpl、CourseDaoImpl 是数据访问层ServletUploadFile 是文件上传的控制器再加上常规的 entity、service、controller 包。我拿到手的第一步不是急着启动而是先把目录结构过一遍确认代码往哪里放。src/main/java/ ├── controller/ # Servlet 层接收请求、调 service、跳转页面 │ ├── StudentServlet.java │ ├── ScoreServlet.java │ └── ServletUploadFile.java ├── service/ # 业务逻辑层事务边界在这里 │ ├── StudentService.java │ └── ScoreService.java ├── dao/ # 数据访问层 │ ├── impl/ │ │ ├── StudentDaoImpl.java │ │ ├── ScoreDaoImpl.java │ │ └── CourseDaoImpl.java │ └── StudentDao.java ├── entity/ # 实体类对应数据库表 │ ├── Student.java │ ├── Course.java │ └── Score.java └── util/ ├── DBUtil.java # JDBC 连接工具 └── StringUtil.java src/main/webapp/ ├── jsp/ # 页面文件 │ ├── studentList.jsp │ ├── scoreList.jsp │ └── scoreAdd.jsp ├── static/ # Bootstrap、jQuery、CSS 等静态资源 └── WEB-INF/ └── web.xml这个结构里最值得关注的是 DAO 层独立出来了。Java Web 课程设计里常见的问题是所有 JDBC 代码全都堆在 Servlet 里页面一多就变成一座没人敢动的屎山。这个项目把数据库操作收拢到 DaoImpl 里Servlet 只做参数接收和页面跳转后期改 SQL 不需要动控制器代码。2.2 Servlet 映射的两种配置方式拿到项目后你需要确认 Servlet 是用注解还是 web.xml 配置的。两种方式在简历上都叫“配置 Servlet 映射”但工作里两种项目都可能碰到。这个项目用的方式看 web.xml 就能定位我一般在代码里两种都保留注释。// 注解方式Servlet 3.0 WebServlet(/score/list) public class ScoreServlet extends HttpServlet { Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取请求参数页码、关键字、学号、课程ID String pageNoStr request.getParameter(pageNo); String studentName request.getParameter(studentName); // 2. 调用业务层查询 ScoreService service new ScoreService(); PageResultScoreVO page service.queryPage(pageNo, pageSize, studentName); // 3. 把数据放到 request 作用域转发给 JSP 渲染 request.setAttribute(page, page); request.getRequestDispatcher(/jsp/scoreList.jsp).forward(request, response); } Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 新增和修改走 POST doGet(request, response); } }这段代码把 Servlet 的职责说得非常清楚拿参数、调服务、存结果、转页面。注意 doPost 里直接复用了 doGet这个做法在查询类接口里很常见但新增和修改接口不要这么干等下发 GET 请求来提交数据数据会穿进 URL 里既难看又不安全。对应的 web.xml 配置则写在 WEB-INF 目录下servlet servlet-namescoreServlet/servlet-name servlet-classcom.example.controller.ScoreServlet/servlet-class /servlet servlet-mapping servlet-namescoreServlet/servlet-name url-pattern/score/list/url-pattern /servlet-mapping两种方式唯一的区别是注册位置不同注解方式写在 Java 文件里web.xml 方式集中在 XML 里。我建议你以注解为主web.xml 只保留 ServletContextListener 之类的全局配置代码和配置离得近排查起来少翻一层。2.3 JSP 和 Servlet 之间的数据交换约定Servlet 处理完业务后数据怎么交给 JSP整个项目里你会反复看到三种写法记住它们的区别就能看懂所有页面。// 写法一请求转发地址栏不变request 作用域的数据可以带到 JSP request.setAttribute(studentList, studentList); request.getRequestDispatcher(/jsp/studentList.jsp).forward(request, response); // 写法二重定向地址栏变化需要重新发一次请求request 里的数据会丢 response.sendRedirect(request.getContextPath() /score/list); // 写法三Session 作用域登录信息、当前用户放这里 HttpSession session request.getSession(); session.setAttribute(currentUser, loginUser);注意forward 和 redirect 是 Java Web 面试里必问的题这个项目里两种都在用你可以对比看效果。常见翻车点是修改完数据后用了 forward刷新页面会重复提交表单。JSP 那边接收数据的固定套路是 JSTL EL 表达式这个项目里 bootstrap 负责样式JSTL 负责循环输出表格行c:forEach items${page.list} varscore tr td${score.studentName}/td td${score.courseName}/td td${score.score}/td /tr /c:forEachEL 表达式里取的是 request.setAttribute 的 keykey 写错就是一片空白这是后面避坑章节要重点讲的第一个问题。项目里 JSP 页面在 webapp/jsp 目录下而静态资源在 static 目录页面里引用 Bootstrap 用的是相对路径部署的时候注意上下文路径别写死成绝对路径。3. 数据库设计三张表、JDBC 连接、分页查询3.1 成绩管理系统的核心表结构成绩管理系统的数据模型不复杂核心就是学生、课程、成绩三张表。成绩表与另外两张表通过外键关联这是最经典的选型——不要为了“看起来高级”把成绩表拆成雪花模型在这个场景没必要。具体字段设计如下表名字段名类型说明studentidINT PK AUTO_INCREMENT学生IDstudent_noVARCHAR(20) UNIQUE学号nameVARCHAR(50)姓名genderCHAR(2)性别clazzVARCHAR(50)班级courseidINT PK AUTO_INCREMENT课程IDcourse_nameVARCHAR(50)课程名称creditDECIMAL(3,1)学分scoreidINT PK AUTO_INCREMENT成绩记录IDstudent_idINT外键指向 student.idcourse_idINT外键指向 course.idscoreDECIMAL(5,2)成绩semesterVARCHAR(20)学期如 2024-2025-1需要注意的一个设计细节是学号加了 UNIQUE 约束性别字段用 CHAR(2) 存“男/女”两个字不要用 TINYINT 存 0/1省得每次查出来还要翻译。成绩字段用 DECIMAL(5,2)能覆盖 0 到 999.99 的范围比 FLOAT 更精确MySQL 里浮点类型做等值比较容易踩坑成绩这种数据要精确到小数点后两位DECIMAL 是正确选择。3.2 JDBC 连接管理MySQL8 的驱动与连接串项目用的是 JDBC 直连 MySQL。JDBC 连接管理是 Java Web 的底层功虽然 MyBatis 帮你封装了大部分操作但连接串、驱动、事务这些概念搞不懂用框架出了问题照样抓瞎。数据库配置一般放在 src/main/resources/db.propertiesjdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/student_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password123456MySQL 5.x 和 8.x 的驱动类名不一样5.x 用 com.mysql.jdbc.Driver8.x 用 com.mysql.cj.jdbc.Driver。如果你导入的是 8.x 的驱动 jar 却写旧类名启动直接报 ClassNotFoundException。连接串里的 serverTimezoneAsia/Shanghai 是必须的不写的话 MySQL 8 会报时区错误后面避坑章节详细讲。对应的连接工具类 DBUtil.javapublic class DBUtil { private static String driver; private static String url; private static String username; private static String password; static { try { // 读取 db.properties 配置文件 InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties); Properties props new Properties(); props.load(in); driver props.getProperty(jdbc.driver); url props.getProperty(jdbc.url); username props.getProperty(jdbc.username); password props.getProperty(jdbc.password); Class.forName(driver); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { // 按逆序关闭先关 ResultSet再关 PreparedStatement最后关 Connection } }代码块里的静态块负责加载驱动读取配置文件。getConnection 每次调用都建立新连接课程设计和毕设这个规模足够了但生产环境不可能这么干那就要换成连接池。你把这个类放在 util 包里所有 DaoImpl 都通过它拿连接后续要换连接池只需要改这一个文件的方法实现上层不用动。3.3 分页查询的 SQL 与参数计算成绩列表页不可能一次把所有数据查出来所以项目里做了分页。分页的 SQL 用 LIMIT 实现前端传页码和后端算偏移量是关键。-- 查询第 2 页每页 10 条 SELECT s.name AS student_name, c.course_name, sc.score, sc.semester FROM score sc JOIN student s ON sc.student_id s.id JOIN course c ON sc.course_id c.id ORDER BY sc.id DESC LIMIT 10, 10; -- 查询总记录数用于计算总页数 SELECT COUNT(*) FROM score;LIMIT 的两个参数含义要理解正确第一个是偏移量第二个是每页条数。偏移量的计算是(pageNo - 1) * pageSize这是新手的经典翻车点经常有人写完页码一翻就少数据。总页数用公式(total pageSize - 1) / pageSize这个向上取整的写法能避免总记录数刚好整除时分页多出一页空页。ScoreDaoImpl 里对应的方法是public ListScoreVO queryPage(String studentName, int pageNo, int pageSize) { // 计算偏移量 int offset (pageNo - 1) * pageSize; StringBuilder sql new StringBuilder(); sql.append(SELECT s.name AS student_name, c.course_name, sc.score, sc.semester ); sql.append(FROM score sc ); sql.append(JOIN student s ON sc.student_id s.id ); sql.append(JOIN course c ON sc.course_id c.id ); sql.append(WHERE 11 ); ListObject params new ArrayList(); if (studentName ! null !studentName.trim().isEmpty()) { sql.append(AND s.name LIKE ? ); params.add(% studentName %); } sql.append(ORDER BY sc.id DESC LIMIT ?, ?); params.add(offset); params.add(pageSize); // 执行查询setObject 逐个填充参数 }注意 SQL 拼接里那个WHERE 11。有人觉得这写法不优雅但在动态条件查询里它有个实际价值后续所有条件都用AND xxx ?直接拼接不需要判断当前是不是第一个条件代码少写三分支可读性反而好。参数全用?占位符通过 PreparedStatement 的 setObject 填充这是防 SQL 注入的标准做法千万别把参数直接拼进 SQL 字符串。4. 核心功能模块实战从表单到数据库的完整链路4.1 成绩录入AddScore 的事务控制成绩录入是整个系统的核心操作涉及检查学生是否存在、检查课程是否匹配、插入成绩三个步骤。这三个步骤必须放在同一个事务里否则会出现“学生不存在但成绩插入成功”这种脏数据。ScoreDaoImpl.addScore 方法的关键代码public boolean addScore(Score score) { Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConnection(); // 开启事务关闭自动提交 conn.setAutoCommit(false); // 第一步检查学生是否存在 ps conn.prepareStatement(SELECT id FROM student WHERE id ?); ps.setInt(1, score.getStudentId()); rs ps.executeQuery(); if (!rs.next()) { return false; // 学生不存在直接返回事务不提交 } // 第二步检查课程是否存在 ps conn.prepareStatement(SELECT id FROM course WHERE id ?); ps.setInt(1, score.getCourseId()); rs ps.executeQuery(); if (!rs.next()) { return false; } // 第三步插入成绩记录 ps conn.prepareStatement( INSERT INTO score (student_id, course_id, score, semester) VALUES (?, ?, ?, ?)); ps.setInt(1, score.getStudentId()); ps.setInt(2, score.getCourseId()); ps.setBigDecimal(3, score.getScore()); ps.setString(4, score.getSemester()); ps.executeUpdate(); // 所有步骤都成功了提交事务 conn.commit(); return true; } catch (Exception e) { e.printStackTrace(); try { // 出错回滚之前执行的语句全部撤销 if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } finally { DBUtil.close(conn, ps, rs); } }事务控制的重点写在注释里了setAutoCommit(false) 是事务开始的标志commit() 在所有操作成功后调用rollback() 在 catch 块里执行。JDBC 默认是每条 SQL 自动提交不加事务的话第一步检查通过了第二步插入失败数据就缺了一条将来统计成绩时这个学生会莫名其妙少一门课。4.2 成绩查询多条件组合与 SQL 注入防护查询功能的坑在“多条件组合”上。学生可能只输入姓名查也可能只选课程查还可能什么都不输入直接点查询按钮。ScoreServlet 中接收参数后统一处理// ScoreServlet.java 的 doGet 方法片段 String studentName request.getParameter(studentName); String courseId request.getParameter(courseId); String semester request.getParameter(semester); // 默认第一页 int pageNo 1; String pageNoStr request.getParameter(pageNo); if (pageNoStr ! null !pageNoStr.isEmpty()) { pageNo Integer.parseInt(pageNoStr); } // 默认每页 10 条 int pageSize 10; // 调 service 层查询得到一个分页结果对象 PageResultScoreVO result scoreService.queryPage(studentName, courseId, semester, pageNo, pageSize); request.setAttribute(pageResult, result); request.getRequestDispatcher(/jsp/scoreList.jsp).forward(request, response);这里参数为空和参数为 null 是两种不同情况。前端表单没填的输入框提交过来是空字符串URL 上根本没带这个参数getParameter 返回 null。DAO 层判断时要先判 null 再判空字符串顺序不能反if (studentName ! null !studentName.trim().isEmpty())有些人只写了! null结果空字符串条件也拼进了 SQL查出来一个空名字的模糊匹配数据量一大直接拖垮查询。对应的 DAO 层动态拼接逻辑是这样的public ListScoreVO queryByCondition(String studentName, String courseId, String semester, int pageNo, int pageSize) { StringBuilder sql new StringBuilder(); ListObject params new ArrayList(); sql.append(SELECT s.student_no, s.name, c.course_name, sc.score, sc.semester ); sql.append(FROM score sc ); sql.append(JOIN student s ON sc.student_id s.id ); sql.append(JOIN course c ON sc.course_id c.id ); sql.append(WHERE 11 ); if (studentName ! null !studentName.trim().isEmpty()) { sql.append(AND s.name LIKE ? ); params.add(% studentName.trim() %); } if (courseId ! null !courseId.isEmpty()) { sql.append(AND sc.course_id ? ); params.add(Integer.parseInt(courseId)); } if (semester ! null !semester.isEmpty()) { sql.append(AND sc.semester ? ); params.add(semester); } sql.append(ORDER BY sc.id DESC LIMIT ?, ?); params.add((pageNo - 1) * pageSize); params.add(pageSize); // PreparedStatement 填充参数 }注意任何从客户端拿来的参数都必须经过白名单校验或者 PreparedStatement 参数化。这个项目里所有条件值都用?占位符你如果拿到别的源码看到字符串用加法拼进 SQL 的直接划走。4.3 Bootstrap 模态框与 jQuery 的异步交互前端部分 Bootstrap 负责视觉jQuery 负责交互。成绩录入页面最常见的交互是点“新增成绩”按钮弹出模态框填完表单点“保存”Ajax 提交到后端成功后刷新表格。这个流程的代码如下!-- 新增成绩模态框 -- div classmodal fade idaddScoreModal tabindex-1 roledialog div classmodal-dialog roledocument div classmodal-content div classmodal-header h5 classmodal-title新增成绩/h5 button typebutton classclose>// 页面加载完成后绑定保存按钮事件 $(function () { $(#saveScoreBtn).on(click, function () { // 收集表单数据 var formData $(#addScoreForm).serialize(); // 校验前端只做基本校验后端必须再次校验 if ($(select[namestudentId]).val() ) { alert(请选择学生); return; } $.ajax({ url: contextPath /score/add, type: POST, data: formData, dataType: json, success: function (res) { if (res.code 200) { alert(录入成功); // 关闭模态框并刷新列表 $(#addScoreModal).modal(hide); location.reload(); } else { alert(res.msg); } }, error: function () { alert(网络异常请稍后重试); } }); }); });这个交互里有个小细节input typenumber设置了 step0.5意味着成绩可以录 85.5 这种小数点后一位的数。后端 DAO 用了 BigDecimal 接收前端 number 类型和后端 BigDecimal 能对齐如果你用 float 接收就会出现 85.5 变成 85.499999 的问题。Bootstrap 模态框第一次打开正常第二次打开没有数据多半是模态框 DOM 被重复初始化了解决方法是把模态框的 HTML 写在页面底部且只写一份。5. 避坑手册这五个问题占了这个项目八成以上的报错5.1 MySQL 驱动加载失败与时区报错现象Tomcat 启动后访问任意页面控制台报 ClassNotFoundException 或者 SQLException 提示 The server time zone value й׼ʱ is unrecognized页面 500。原因这个坑有两个源头。第一MySQL 5.x 和 8.x 的 JDBC 驱动类名不同5.x 用 com.mysql.jdbc.Driver8.x 用 com.mysql.cj.jdbc.Driver装错 jar 用错类名就报找不到类第二MySQL 8 对连接串有时区校验连接串里没指定 serverTimezone 就会报时区不识别而中文操作系统的默认时区会变成乱码。解决确认 lib 目录下是哪个版本的 mysql-connector-java驱动类名和驱动版本对齐连接串固定带上?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue这一段。allowPublicKeyRetrieval 是 MySQL 8 才需要的不加在某些版本下会报 Public Key Retrieval is not allowed。5.2 中文乱码POST 和 GET 的字符集要分开处理现象从页面新增一个叫“张三”的学生保存后再查出来变成“å¼ ä¸‰”控制台打印的参数也是乱码。原因HTTP 请求的字符集处理分两条线。POST 请求的字符集由 request.setCharacterEncoding(UTF-8) 决定需要在读取任何参数之前调用GET 请求的参数在 URL 上Tomcat 8 之前默认用 ISO-8859-1 解码Tomcat 8 之后默认 UTF-8。如果你把项目部署在老版本 Tomcat 上GET 请求的中文参数必乱。解决加一个 CharacterEncodingFilter 统一处理在 web.xml 里配置 filter 映射filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping如果你不想引入 Spring 的类自己写个 filter 也很快核心就一行代码request.setCharacterEncoding(UTF-8)。写完后别忘确认三点数据库连接的 characterEncodingutf8、JSP 页面顶部% page contentTypetext/html;charsetUTF-8 %、HTML 里 meta 标签 charset 是 UTF-8三者缺一不可。5.3 JSP 页面 EL 表达式全部空白现象页面结构、表格、按钮都渲染出来了但是学生姓名、成绩这些动态数据位置全是空的控制台也不报错。原因最常见的两个原因。一是 JSP 页面开头少了% page isELIgnoredfalse %或者 web.xml 里配置了 Servlet 2.3 的 DTD那个版本默认 EL 不启用二是 request.setAttribute 的 key 和 JSP 里 EL 表达式写的 key 不一致比如 Servlet 里放的 studentListJSP 里写${students}取出来当然是空。解决先打开 JSP 看头部有没有 isELIgnored 设置没写就补上再对比 Servlet 里的 setAttribute key 和页面里的 EL 变量名。排查的时候我习惯在页面临时加一行${pageScope}能输出当前请求作用域里所有对象一眼就能看出数据到底在不在、key 叫什么。5.4 Bootstrap 模态框点保存按钮出现重复提交现象新增成绩点了两次保存数据库里出现两条一模一样的记录。原因这个坑藏在 JavaScript 事件绑定里。某些写法在模态框每次打开时重新绑定一次 click 事件第二次打开时按钮上一个事件没解绑又绑了一个点一下执行两次。另一个原因是表单提交后没有禁用按钮或移除事件用户手快连点两下。解决把事件绑定放在$(function(){})初始化函数里只绑一次或者在保存按钮点击后立即加上$(this).prop(disabled, true)等 Ajax 返回成功再恢复。更稳妥的做法是提交成功后直接location.reload()刷新整个页面让所有状态重置这个处理最简单也最不容易出问题。5.5 上传的文件重启 Tomcat 就丢了现象项目里有文件上传功能上传成功后本地能看到文件但一重启 Tomcat 文件就消失或者项目重新部署后文件全没了。原因这个项目里 ServletUploadFile 处理文件上传如果代码里把文件保存到了 Tomcat 的临时目录或者项目发布目录下Tomcat 重启时会清理临时目录重新部署时会覆盖整个 webapps 目录保存的文件自然就没了。解决文件要保存到 Tomcat 之外的固定路径比如 Windows 下的 D:/uploadLinux 下的 /data/upload。常见做法是在服务器上建一个独立目录通过配置文件配置上传路径页面访问时用虚拟目录映射Context docBaseD:/upload path/upload reloadabletrue/这段配置加在 Tomcat 的 server.xml 的 Host 节点下就能把 D:/upload 目录映射成 /upload 虚拟路径页面里通过/upload/文件名访问到磁盘上的文件。课程设计里很多人忽略这一点答辩时演示上传没问题老师一重启项目文件就没了相当尴尬。6. 部署验收与升级路线把系统跑起来并验证核心链路6.1 打包部署到 Tomcat 的两种方式拿到源码后第一件事是让它跑起来。这个项目是传统 JSP 项目部署方式和 Spring Boot 不一样需要打包成 war 放进 Tomcat 的 webapps 目录。用 IDEA 打 war 包的操作是File - Project Structure - Artifacts - 点加号选 Web Application: Archive - 勾选 Build on make - 然后 Build - Build Artifacts产物在项目根目录的 out/artifacts 下。# 把 war 包复制到 Tomcat 的 webapps 目录下 cp target/student-score-manager.war $CATALINA_HOME/webapps/ # 启动 Tomcat $CATALINA_HOME/bin/startup.sh # 查看启动日志 tail -f $CATALINA_HOME/logs/catalina.out启动完成后访问地址是http://localhost:8080/student-score-manager/注意项目名一定要对上部署多个应用时访问路径是 war 包名。如果 404先看 catalina.out 里项目有没有成功部署再确认 URL 里的上下文路径和 war 包名一致。IDEA 里也可以配置 Tomcat 直接跑但找工作面试聊到部署war 包手动部署这一套得会很多小公司生产环境还在用这种方式。6.2 核心功能验收清单系统跑起来后按下面的清单走一遍能覆盖这个项目主要的业务逻辑功能路径操作预期结果登录输入管理员账号密码进入系统主页Session 中保存用户信息学生管理新增学生张三班级 软件2301列表中出现张三刷新后仍在成绩录入给张三录一门 Java 课程成绩 85.5成绩表新增一条记录无报错多条件查询按姓名“张”查只显示姓张的学生成绩分页正常统计页面查看某课程的平均分、及格率数据与手算结果一致文件上传上传一张学生照片重启 Tomcat 后照片仍能访问退出登录点击退出返回登录页直接访问主页被拦截走完这个清单项目的主要功能就验证完成了。这里想提一个判断源码质量的方法功能能跑只是底线你还需要确认事务有没有加、SQL 参数有没有用占位符、重要操作有没有日志记录。这三个点决定了一个项目是课程设计还是能上生产。6.3 后续升级路线如果你想把这份源码变成自己的毕设或者面试项目有两条升级路径值得考虑。第一条是轻量改造保持 Servlet JSP 骨架不变把 JDBC 换成连接池。阿里开源的 Druid 或者 HikariCP 在 pom.xml 加依赖把 DBUtil 里 DriverManager.getConnection 换成 DataSource.getConnection项目性能立刻不同。第二条是逐步替换保持功能不变把 DAO 层换成 MyBatis让 SQL 从 Java 代码里解放出来写 XML 映射文件方便后期调优。这条路线可以让你在面试里说“我用传统的 JDBC 做过也用过 MyBatis能讲清楚两者的适用边界”。升级的时候有个执行顺序先加日志框架再换连接池最后动 DAO 层。日志标记是从“能跑”到“可维护”的分水岭打印参数和 SQL 对排查问题帮助很大。系统上线前把 System.out.println 全部换成 logback 或 log4j这是一个 Java 开发者的基本功。这个项目我从拿到到完全跑通花了不到一个小时其中半小时在处理 MySQL 时区和驱动版本的问题。从那以后我每拆一个 Java Web 项目第一件事就是先看 db.properties 和 pom.xml / lib 目录确认驱动版本和连接串对得上再去启动 Tomcat省掉了大半的黑匣子疑惑。项目的骨架你已经清楚了照着上面的步骤跑一遍希望帮到你。本文还有配套的精品资源点击获取
返回列表