
简介这是基于Java EE的网上评教系统完整工程融合JSP动态页面与MySQL数据库面向Java Web初学者和需要做课程设计、毕业设计的学生。系统包含学生登录、课程选择、评教打分、结果统计等核心功能覆盖前端展示、后端控制与数据存储清晰演示了MVC分层实现方式。压缩包共651个文件大小约11.71MB主要含101个JSP页面、35个CSS样式、21个JavaScript脚本、391张GIF图片另有SQL数据库脚本、配置文件及运行所需的JAR包目录结构完整可导入IDE直接部署数据库设计包含用户、课程、教师、评教等核心数据表通过外键关联实现数据一致性为学习者提供了规范的关系模型范本。目前已有110人学习使用适合参考代码、复现项目或二次开发通过这份资源能获得一套可运行的评教系统源码学习JSP与MySQL的整合技巧以及表单处理、会话管理和数据库关联设计等实战方法是快速提升Java Web开发能力的实用素材。1. 基于JavaEE的网上评教系统课程设计里最容易做出来却讲不清楚的一个方向每年课程设计答辩季总有一批人能做到“系统能跑、页面能点、分数能存”但被老师问一句“你的防重复评教是怎么做的”就卡住。基于JavaEE的网上评教系统就是这样一个典型题目它用JSP做前端页面展示用Servlet处理请求用MySQL存储学生、教师、课程和评教数据整个链路恰好覆盖了JavaWeb课程的核心知识点难度又控制在一个人能独立完成的范围内。这个项目解决的真实问题是把传统纸质评教从“填表、回收、手工统计”变成“在线打分、自动汇总、按教师可查”的闭环适合正在做JavaWeb课程设计或毕业设计的学生也适合想用最朴素的ServletJSP方式理解Web应用分层的新手。但正因为题目常见答辩时老师不会只问“怎么跑的”更会问“为什么这么设计”所以把整个系统从架构到数据表的逻辑理顺比把页面做得花哨重要得多。2. 从JSP到Servlet再到JDBCJavaEE评教系统的三层架构怎么落2.1 为什么采用“JSPServletJDBC”而不上Spring Boot很多人在选题后会纠结既然Spring Boot现在这么普及为什么不直接用它我的建议是除非课程明确允许否则课程设计题目里写了“JavaEE”和“JSP”就按传统JavaEE的套路来做。原因有两个一是答辩时老师大概率按JSP、Servlet、JDBC这条线来追问你用一个Spring Boot把所有东西包起来反而容易被追问框架内部机制答不上来会显得项目不是自己写的二是这个系统的规模很小主体逻辑就是登录、评教、统计、查询Servlet加JSP完全够用引入Spring Boot反而多出一堆配置和依赖部署时还要处理jar包和外部Tomcat的兼容问题属于给自己添麻烦。传统的“JSPServletJDBC”三层分工非常清楚JSP负责页面展示和表单渲染Servlet负责接收请求、调用业务逻辑、决定跳转到哪个页面JDBC负责与MySQL交互。常见的做法是在Servlet层之上再包一层Service业务逻辑层虽然系统小的时候Service层看起来有点多余但我建议还是保留它。评教系统里有一个典型的业务逻辑是“提交评教前先判断这个学生是否已经评过这门课”这个判断放在Servlet里也能写但放在Service里会更清晰答辩时也更容易说明白“业务逻辑和数据访问是分开的”。2.2 项目目录结构按角色分包还是按功能分包在Eclipse或IDEA里新建动态Web项目后目录结构一般长这样src下放Java源码WebContent或webapp下放JSP页面、CSS、JS和WEB-INF。Java包的结构我见到比较多的课程设计是下面这种按层分包的方式com.school.evaluation ├── entity // 实体类Student, Teacher, Course, Evaluation等 ├── dao // 数据访问接口与实现 ├── service // 业务逻辑层 ├── servlet // 控制器层 ├── util // DBUtil、字符串处理等工具类 └── filter // 登录过滤器可选按层分包的好处是代码职责一目了然新手不容易迷路。也有按角色分包的做法比如student、teacher、admin三个包各自放Servlet和页面这种写法在小项目里看着直观但一旦涉及共享的实体类和DAO很容易出现类被复制多份的问题。我更推荐按层分包因为评教系统的学生、教师、管理员本身共用大量基础代码比如核心的登录逻辑和数据库连接工具按层分包能让复用关系更自然。实体类要严格对应数据库表结构。比如学生表里有学号、姓名、密码、班级、专业实体类就写五个字段加上无参构造、带参构造和getter/setter。有个小习惯值得养成实体类字段的命名用驼峰数据库字段用下划线比如数据库字段student_noJava属性写成studentNo在DAO层做ResultSet取值时用rs.getString(student_no)手动映射一次就行清晰明了比强行让两边命名一致更符合MySQL的命名习惯。2.3 数据库连接工具类封装好JDBC后面少踩一半坑整个系统里所有的DAO都要获取数据库连接如果每个DAO都写一遍DriverManager.getConnection后面改密码或换数据库时就要全局搜索替换非常被动。常见的做法是写一个DBUtil工具类把加载驱动和获取连接统一收口。连接参数不写死在Java代码里而是放在src下的db.properties配置文件中jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/eval_db?useSSLfalsecharacterEncodingutf8 jdbc.usernameroot jdbc.password123456对应的DBUtil.java核心代码public class DBUtil { private static String driver; private static String url; private static String username; private static String password; static { try { // 读取 src 下的配置文件键值对加载到 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(ResultSet rs, Statement stmt, Connection conn) { if (rs ! null) { try { rs.close(); } catch (SQLException e) {} } if (stmt ! null) { try { stmt.close(); } catch (SQLException e) {} } if (conn ! null) { try { conn.close(); } catch (SQLException e) {} } } }这里有两个参数要特别注意。jdbc.url里的useSSLfalse是告诉MySQL驱动不要启用SSL握手MySQL 8.x 对SSL的默认要求变严了不加这个参数在部分环境下手动部署会报警告甚至连接失败characterEncodingutf8保证数据库读写中文不乱码和页面编码、响应编码一起配合才能彻底解决乱码问题。驱动类名com.mysql.jdbc.Driver对应的是 mysql-connector-java 5.x 版本如果你用的是MySQL 8.x 的驱动包mysql-connector-java 8.0.x类名要改成com.mysql.cj.jdbc.Driver并在jdbc.url里额外加上serverTimezoneAsia/Shanghai否则会报时区错误。这两个版本差异是评教系统部署时最常见的翻车点后面避坑章节还会再提到。3. 评教系统的数据库设计谁评谁、评什么、怎么算分3.1 六张核心表的字段设计与外键关系评教系统的数据模型核心是回答三个问题谁在评学生、评的是谁教师、评了什么课程和指标。基于这个逻辑我习惯设计六张表t_admin、t_student、t_teacher、t_course、t_evaluation_item、t_evaluation_result。其中t_evaluation_result要拆成主表和明细表在这个系统里我建议不拆而是把每一条评教记录设计成“一个学生对一门课程的一次评教”一次评教包含多个指标的分值用逗号分隔存进一个字段虽然省事但后续统计某个指标的平均分时写SQL会非常痛苦。正确的做法是设计t_evaluation_result主表记录“谁评了谁”再设计一张t_evaluation_detail明细表记录“各指标打了多少分”。主表字段如下字段名类型含义idINT 自增主键评教记录IDstudent_idVARCHAR(20)评教学生学号外键关联t_studentcourse_idINT被评课程ID外键关联t_coursecreate_timeDATETIME评教时间UNIQUE(student_id, course_id)联合唯一防止同一学生对同一课程重复评教明细表t_evaluation_detail字段名类型含义idINT 自增主键明细IDresult_idINT关联主表iditem_idINT指标ID外键关联t_evaluation_itemscoreTINYINT该指标得分1~10或1~5按指标满分定为什么不把指标得分直接冗余在主表里因为评教指标是可能调整的今年用“教学态度、内容讲解、课堂互动”三个指标明年可能改成四个。指标单独建表后调整指标不用改评教记录的表结构统计时通过关联查询算平均分。这就是典型的“把变化的部分做成数据而不是做成字段”的设计思路。3.2 建表SQL主外键约束和唯一约束怎么加建表SQL是评教系统手册环节的重头戏建议直接在Navicat或命令行里执行执行前确认数据库字符集是utf8mb4否则中文数据和表情符号可能存不进去。核心建表语句如下CREATE DATABASE IF NOT EXISTS eval_db DEFAULT CHARACTER SET utf8mb4; USE eval_db; CREATE TABLE t_student ( student_no VARCHAR(20) PRIMARY KEY, password VARCHAR(64) NOT NULL, student_name VARCHAR(40) NOT NULL, class_name VARCHAR(40), major VARCHAR(50) ); CREATE TABLE t_teacher ( teacher_no VARCHAR(20) PRIMARY KEY, password VARCHAR(64) NOT NULL, teacher_name VARCHAR(40) NOT NULL, title VARCHAR(30) ); CREATE TABLE t_course ( course_id INT AUTO_INCREMENT PRIMARY KEY, course_name VARCHAR(60) NOT NULL, teacher_no VARCHAR(20), FOREIGN KEY (teacher_no) REFERENCES t_teacher(teacher_no) ); CREATE TABLE t_evaluation_item ( item_id INT AUTO_INCREMENT PRIMARY KEY, item_name VARCHAR(80) NOT NULL, max_score TINYINT NOT NULL DEFAULT 10 ); CREATE TABLE t_evaluation_result ( id INT AUTO_INCREMENT PRIMARY KEY, student_no VARCHAR(20), course_id INT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_student_course (student_no, course_id), FOREIGN KEY (student_no) REFERENCES t_student(student_no), FOREIGN KEY (course_id) REFERENCES t_course(course_id) ); CREATE TABLE t_evaluation_detail ( id INT AUTO_INCREMENT PRIMARY KEY, result_id INT, item_id INT, score TINYINT, FOREIGN KEY (result_id) REFERENCES t_evaluation_result(id), FOREIGN KEY (item_id) REFERENCES t_evaluation_item(item_id) );这里有两个设计细节值得在答辩时主动讲一是t_evaluation_result上的联合唯一约束uk_student_course它从数据库层面杜绝了同一学生对同一门课的重复评教即使Servlet层忘记做判重逻辑数据也进不来二是外键约束保证了评教记录不会指向不存在的学生或课程数据库的引用完整性由引擎兜底。还有一点要注意不要在子表外键上写ON DELETE CASCADE。评教历史数据是需要留存回溯的管理员误删一个学生时如果级联删掉他的评教记录整条统计链就断了宁可让删除操作失败也不要做级联清空。3.3 评教总分统计SQL平均分、参评率、指标换算评教系统做完后教师端和管理员端最核心的功能就是统计查询。最常见的需求是按课程统计每位教师的平均分、参评人数和各项指标得分。因为指标数和满分值可能不同统计时要按比例换算成百分制。核心SQL如下SELECT c.course_name, t.teacher_name, COUNT(DISTINCT r.student_no) AS student_count, ROUND(SUM(d.score * 100 / i.max_score) / COUNT(d.id), 1) AS avg_score FROM t_course c JOIN t_teacher t ON c.teacher_no t.teacher_no JOIN t_evaluation_result r ON c.course_id r.course_id JOIN t_evaluation_detail d ON r.id d.result_id JOIN t_evaluation_item i ON d.item_id i.item_id GROUP BY c.course_id, t.teacher_no;逻辑说明先通过JOIN把评教结果和明细指标关联起来d.score * 100 / i.max_score把每个指标的原始分换算成百分制得分SUM除以明细条数得到平均分COUNT(DISTINCT r.student_no)统计实际参评人数。参评率需要额外拿到应评人数选了这门课的学生总数这时要用一门课的选课表来算。如果系统没有设计选课表常见的做法是在t_course里加一个expected_count字段由管理员录入应评人数。但这种方式数据维护成本高我一般在系统里加一张t_course_student选课中间表学生选了课才出现在“待评课程”里评教人数和应评人数都直接查这张表逻辑更圆满不过这会让工作量增加如果课程设计时间紧用expected_count字段顶着也能讲得通。4. 核心功能链路从登录到提交一次评教要写哪些Servlet4.1 登录逻辑Session管理角色的分发入口评教系统有学生、教师、管理员三类角色登录入口可以统一到一个页面通过用户名前缀或下拉框区分角色。我习惯用统一登录接口加角色下发的做法用户提交账号密码后Servlet依次去学生表、教师表、管理员表里查查到就把账号存入Session并带上角色标识查不到就返回登录页。登录的核心代码protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); String role request.getParameter(role); // student / teacher / admin // 按角色选择查询表避免一次拼SQL把三张表都查一遍 if (student.equals(role)) { Student student studentDao.findByUsernameAndPassword(username, password); if (student ! null) { request.getSession().setAttribute(loginUser, student); request.getSession().setAttribute(role, student); response.sendRedirect(student/index.jsp); // 跳转到学生主页 return; } } // teacher 和 admin 的判断逻辑类似 request.setAttribute(error, 用户名、密码或角色不正确); request.getRequestDispatcher(login.jsp).forward(request, response); }代码逻辑本身很简单但有两个点值得展开。第一是密码存储一定要做处理常见做法是存MD5或SHA-256摘要不要明文存储即使用户表被脱库也不会直接泄露明文密码。第二是登录成功后跳转要用sendRedirect而不是forward因为跳转后浏览器地址栏会变成目标页面URL刷新时不会重复提交登录表单。Session里存的是实体对象而不是用户名字符串这样后续业务代码里要取学号或教师编号时直接getAttribute(loginUser)后转成对应实体就能拿到少一层查询。页面间访问控制是容易被忽略的点。学生没登录直接访问student/index.jsp是能打开的因为JSP是静态资源不经过Servlet。常见的做法是写一个LoginFilter实现javax.servlet.Filter在doFilter里判断Session中是否存在loginUser不存在就重定向到登录页。filter的拦截规则配成拦截所有请求然后根据URL前缀判断该路径允许哪些角色访问。这个过滤器加完系统才算有基本的访问安全边界。4.2 评教提交多指标打分的事务写法与防重复提交评教提交是整个系统最核心的Servlet也是答辩必问的环节。业务流程是学生登录后进入待评课程列表点击“评教”进入评分页页面上列出所有评教指标和对应的打分控件提交时Servlet一次性接收课程ID和所有指标分数先判断该学生是否评过这门课没评过则插入主表记录和明细记录。因为要同时操作两张表必须用事务保证一致性否则可能出现主表插入成功但明细失败评教记录不完整的情况。提交逻辑如下protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session request.getSession(false); Student student (Student) session.getAttribute(loginUser); String courseIdStr request.getParameter(courseId); String[] itemIds request.getParameterValues(itemId); String[] scores request.getParameterValues(score); if (itemIds null || scores null || itemIds.length ! scores.length) { request.setAttribute(error, 评教数据不完整请重新评分); request.getRequestDispatcher(evaluate.jsp).forward(request, response); return; } // 业务层判断同一学生同一课程是否已评教 EvaluationService service new EvaluationService(); if (service.isEvaluated(student.getStudentNo(), Integer.parseInt(courseIdStr))) { request.setAttribute(error, 你已评教过该课程); request.getRequestDispatcher(evaluate.jsp).forward(request, response); return; } Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); // 插入主表获取生成的自增主键 int resultId service.insertResult(conn, student.getStudentNo(), Integer.parseInt(courseIdStr)); // 批量插入明细表 for (int i 0; i itemIds.length; i) { service.insertDetail(conn, resultId, Integer.parseInt(itemIds[i]), Integer.parseInt(scores[i])); } conn.commit(); response.sendRedirect(student/courseList.jsp); } catch (SQLException e) { conn.rollback(); e.printStackTrace(); request.setAttribute(error, 提交失败请重试); request.getRequestDispatcher(evaluate.jsp).forward(request, response); } finally { DBUtil.close(null, null, conn); } }关于这段代码有三处参数和习惯要说明。第一request.getParameterValues拿到的是同名的多个值比如多个指标打分项都用nameitemId所以页面上每条指标的隐藏域必须按同一个name提交否则取值会出现空指针。第二事务开启后conn要一路传到Service方法和DAO方法里不能在不同的方法里各取各的连接否则提交和回滚就不是同一个事务这是新手最容易写错的地方。第三rollback放在catch块里一旦明细插入中途出错主表记录也要回滚掉否则会留下一条没有明细的脏数据统计评教人数时就会多出一个虚假的参评记录。除了业务层的判重数据库层面的唯一约束是最后的底牌。业务判重和数据库约束同时存在时并发场景下两条请求同时进来可能都通过了业务判断但数据库的唯一索引会拒绝第二条插入并抛异常所以异常处理里一定要把DuplicatedEntryException这类情况映射成“你已评教过该课程”的友好提示而不是直接给用户看一个500页面。4.3 JSP页面与表单回显页面怎么把动态数据渲染出来JSP页面在评教系统里承担两大职责展示列表和渲染表单。待评课程列表页用到了典型的循环输出我的做法是Servlet查询出List后存入request域JSP通过JSTL标签遍历。这里有一个关键取舍用原始的% for(int i0; ilist.size(); i){ %脚本片段能跑但页面里嵌Java代码会让JSP显得混乱且答辩时容易被追问为什么不用EL表达式。用JSTL的写法更标准也符合JavaEE规范对表现层的要求。课程列表页核心代码% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table trth课程名/thth授课教师/thth操作/th/tr c:forEach items${courseList} varcourse tr td${course.courseName}/td td${course.teacherName}/td td a href${pageContext.request.contextPath}/evaluateServlet?courseId${course.courseId} 去评教 /a /td /tr /c:forEach /table${pageContext.request.contextPath}这一行值得记牢它动态获取项目的上下文路径也就是部署时Web应用的访问根路径。字面量写法/网上评教系统/evaluateServlet一旦项目改名或换部署环境就404用contextPath拼接就能保证链接在任意部署名环境下都正确这个细节是答办老师很爱看的。评教表单页则是把指标从数据库查出来后循环输出每个指标对应一个名称和一组单选项提交时用隐藏域带上指标IDc:forEach items${itemList} varitem div span${item.itemName}/span input typehidden nameitemId value${item.itemId} / c:forEach begin1 end${item.maxScore} varscore labelinput typeradio namescore value${score} / ${score}分/label /c:forEach /div /c:forEach这里的nameitemId和namescore是多个同名控件正好对应对应Servlet里getParameterValues(itemId)的取值方式。注意每一条指标都要有一个隐藏域提交itemIdServlet端收到的itemId数组和score数组长度应该相同如果页面漏写了某个隐藏域两边数组长度不一致就会触发前面的数据完整性校验并提示用户重新评分。5. 部署与避坑Tomcat、MySQL、乱码与路径的七条记录5.1 现象启动Tomcat时8080端口被占用部署项目时最常见的第一个报错是Tomcat启动失败控制台提示Port 8080 required by Tomcat v9.0 Server at localhost is already in use。原因几乎都是上一次启动没有正常关闭或者另一个程序占用了端口。解决方法是找到占用进程并结束它Windows下在命令行执行netstat -ano | findstr 8080看到占用PID后用taskkill /f /pid 进程号杀掉。如果你是开发环境里用Eclipse内置的浏览器调试也可以把Tomcat服务器配置里的端口改成8081或9090但改完要记得访问地址一起变。5.2 现象连接数据库报错驱动类和时区都提示找不到MySQL 8.x 环境下连接失败报错通常包含ClassNotFoundException: com.mysql.jdbc.Driver或The server time zone value ... is unrecognized。原因是驱动包版本和类名不匹配。5.x的驱动用com.mysql.jdbc.Driver8.x的驱动用com.mysql.cj.jdbc.Driver同时url要追加serverTimezoneAsia/ShanghaiuseSSLfalse。解决办法是检查lib目录下放的jar包版本把db.properties改成对应配置改完重启Tomcat不用重启MySQL。这类问题属于环境配置不匹配和代码逻辑无关耐心读报错信息就能解决。5.3 现象页面显示中文全是问号或乱码乱码问题有三个来源。第一个来源是JSP页面本身的编码页面顶部要有% page contentTypetext/html;charsetUTF-8 languagejava %否则浏览器按系统默认编码可能是GBK解析导致乱码。第二个来源是POST请求提交中文Servlet里必须在读取任何参数之前执行request.setCharacterEncoding(UTF-8)。第三个来源是数据库连接的url没有加characterEncodingutf8参数导致写入MySQL时按latin1存储。排查时按页面→请求→数据库的顺序逐个检查不要只在一个地方改编码那样往往无效。5.4 现象同一个学生重复评教成功出现了多条记录业务的判重写在Servlet里但并发或用户双击提交按钮时仍可能绕过判重逻辑造成重复记录。这时要确认数据库表上是否真的建了联合唯一约束。排查方法是查看建表语句SHOW CREATE TABLE t_evaluation_result如果没有UNIQUE KEY uk_student_course就用ALTER TABLE t_evaluation_result ADD UNIQUE KEY uk_student_course (student_no, course_id)补上。数据库约束加好后即使业务层判重失效插入也会抛异常异常被catch后转成友好提示避免了脏数据。这里的教训是前端按钮禁用、业务层判重都是软性防线数据库唯一约束才是硬性兜底。5.5 现象页面报404文件在项目里明明是有的404分两种情况。第一种是JSP文件放在WEB-INF目录下然后你尝试通过浏览器URL直接访问Tomcat出于安全考虑禁止外部直接访问WEB-INF下资源所以一定会404。解决办法是用Servlet转发访问request.getRequestDispatcher(/WEB-INF/jsp/courseList.jsp).forward(request, response)。第二种是项目部署到Tomcat后访问路径不对地址栏前缀必须是http://localhost:8080/项目上下文路径/上下文路径默认是项目名和你在Eclipse里看到的项目名可能大小写不一致直接复制URL最稳。5.6 现象评分能正常提交但统计结果全是NULL或偏小统计SQL没报错但结果异常多半是数据或数据类型的问题。AVG(score)对没有任何评教记录的课程会返回NULL页面显示空应该用ROUND(IFNULL(AVG(...), 0), 1)或COALESCE处理。另外明细表的score字段用小数浮点类型存的话统计时可能出现精度误差建议用TINYINT存整数分计算时按比例换算成百分制。还有一种情况是分组时漏了课程名和教师名导致统计结果diff到不到具体哪门课这属于SQL的GROUP BY写法问题把course_id和teacher_no都放进GROUP BY并直接SELECT名字字段就能避免。5.7 现象登录成功后跳转页面但页面上的用户姓名显示不出来这通常是Session取值或EL表达式的问题。JSP页面一般放在WebContent根目录而不是WEB-INF下但无论放哪取值方式都是sessionScope.loginUser.studentName或${sessionScope.student.studentName}。如果显示空白先确认登录Servlet里往Session放的是什么keysetAttribute(loginUser, student)对应EL就是${sessionScope.loginUser.studentName}如果放的是student对象那就写${sessionScope.student.studentName}两边不一致就会出现空白不报错。另一个原因是没有引入JSTL标签库或EL表达式被禁用检查页面顶部是否声明了% taglib %以及web.xml里的Servlet版本是否在2.4以上低于2.4默认会忽略EL。6. 答辩验证与进阶方向让评教系统从能跑变成能讲清楚6.1 按角色设计一条完整演示主线演示不要东点一下西点一下我建议按“管理员建数据→学生评教→教师看结果”这条主线走。管理员登录先进课程管理页面把学生、教师、课程、评教指标的数据补齐或展示出来说明维护入口。然后切换学生账号展示待评课程列表、进入评分页、打分提交提交后再进同一门课让页面提示“已评教过”这一步生动展示了防重复评教的效果。最后切换教师账号展示统计页面的平均分和参评人数讲清楚这个平均分是怎么算出来的。整套演示控制在8分钟以内重点讲两件事事务保证评教数据完整、唯一约束兜底防重复。6.2 用三组SQL验证业务闭环答辩前建议自己先跑三组SQL这样被老师追问时心里有底。第一组查所有未评教的课程找出“某学生应该评但还没评”的课程列表对应待评页面数据对不对第二组查某门课的各指标平均分验证明细表的score统计第三组查所有教师按平均分排名看是否有NULL值出现。三组SQL跑完系统的数据链路就是通的了。6.3 从JSP向Spring Boot迁移时哪些代码能复用如果后续要做毕业设计或想把它改成Spring Boot版本实体类、数据库设计、业务逻辑层这三大块几乎可以原样搬过去只有Servlet要改造成ControllerJSP里的JSTL保留但改成模板引擎或前后端分离接口。数据库表结构和SQL语句一行都不用改因为表设计时没有依赖JSP技术指标独立成表、联合唯一约束兜底这些设计在框架迁移后依然是核心资产。这个角度答辩时提前说出来会给老师留下“这个人理解迁移成本”的印象。我自己最早做这个题目时把大部分时间花在了调整页面样式上数据库建表很随意防重复只写了一行if(已评教) return结果并发场景被老师一问就露了底。后来重做了数据库约束和事务才真正明白一个能讲清楚的系统远比一个好看的页面值钱。希望这些拆解能帮你把这个经典题目做成答辩时敢拍胸脯的项目。本文还有配套的精品资源点击获取