ARTICLE DETAIL

资讯详情

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

JSP+Servlet+JDBC课程设计:CIMS系统源码解析与避坑指南

JSP+Servlet+JDBC课程设计:CIMS系统源码解析与避坑指南 简介基于JSP技术实现的CIMS课程设计源码面向高校计算机专业学生目标是帮助初学者理解动态网站开发与业务系统设计流程可用于课程设计、毕业设计或实训项目参考。压缩包共有212个文件大小15.2MB以XML配置文件、JAR依赖包、JSP页面、JAVA源文件为主同时包含编译后的CLASS文件、CSS样式表、JavaScript脚本、属性文件与SQL脚本等辅助资源目录结构清晰可以按模块对照学习。当前已有103人学习下载。项目完整展示了从前端页面展示、后端业务逻辑到数据库操作的实现细节代码采用控制器、服务层等分层写法涵盖用户管理、客户管理等典型功能通过阅读和运行源码能够掌握JSP页面中标签使用、请求处理、前端样式与后端协同开发的思路并能结合SQL脚本理解数据初始化过程对课程设计的实现与排错有直接参考价值适合边阅读边动手实践。1. JSP写的CIMS课程设计先弄清楚这个缩写到底指什么CIMS这个缩写在不同课程里指的东西差别很大。在制造业信息化课程里它是Computer Integrated Manufacturing System计算机集成制造系统但在Java Web课程设计最常见的使用场景里它是Course Information Management System课程信息管理系统。不管是哪一种落到JSP这门课上核心要解决的问题是一样的用JSPServletJDBC这套技术栈做一个能登录、能增删改查、有权限区分、能导出统计结果的完整Web项目。这套源码的价值不在于用了多新的框架而在于它是课程设计里最标准的三层结构教师验收时能一步步讲清楚请求怎么走、数据怎么存、权限怎么控。适合看这篇文章的人是两类。一类是正在做Java Web课程设计的学生手里拿到一套带完整源码的项目但不知道从哪开始看、改哪些地方能变成自己的设计另一类是想自己从零搭建CIMS但不想一上来就上SpringBoot的初学者想先理解Servlet和JSP的原生工作机制。本文不教你怎么写“精美”的框架代码只讲怎么把一套基于JSP的CIMS课程设计源码跑起来、看得懂、改得动、答得上老师的提问。2. 这套源码的技术栈为什么是JSPServletJDBC选型理由与项目结构2.1 课程设计不选SpringBoot的四个现实理由我在带Java课程设计时最常被问的一句话是“老师我能不能直接用SpringBoot写”能但我不建议你用。不是SpringBoot不好而是课程设计的验收指标通常不是“用了最新框架”而是“这个项目是不是你自己写的、核心逻辑能不能讲清楚”。SpringBoot的自动配置让人省心但也把Servlet的生命周期、请求转发、Session管理、JDBC连接这些最基础的知识盖住了。答辩时被问一句“你的登录校验是怎么拦截的”“你的数据库连接有没有关闭”如果答不上来框架反而成了减分项。第二个理由是部署环境的不可控性。课程设计实验室里的Tomcat版本、JDK版本经常和你在家用的不一样。JSPServletJDBC这套组合对版本兼容性要求很低JDK 8、Tomcat 8.5/Tomcat 9就能稳定运行。而SpringBoot项目一旦JDK版本对不上启动直接报错答辩现场翻车是常有的事。第三个理由是代码量。CIMS课程设计的功能范围一般是用户登录、课程信息维护、选课退课、权限区分、简单统计。用JSPServletJDBC写整个项目的文件数在20到40个之间每个文件都能在答辩时单独展示。用SpringBoot写自动生成的目录结构本身就会让一部分代码藏进框架里讲解时反而要解释“这里为什么不用写代码”比解释“这段代码做了什么”更费劲。第四个理由是源码量。课程设计源码的评分通常会看代码的完整度和注释量JSP项目的代码是摊开来的从DBUtil到Servlet到JSP页面每一层都能看到实际内容。SpringBoot项目里大量逻辑靠注解驱动看源码的人如果对注解不熟读起来很吃力。所以在课程设计这个特定场景下JSPServletJDBC不是退步而是最稳妥的选择。2.2 项目目录结构与URL映射规划一套合格的CIMS源码目录结构应该遵循“WebContent或src/main/webapp下放JSP页面、src下放Java类、WEB-INF/lib下放依赖Jar包”的约定。我见过很多学生的源码翻车不是代码写错而是JSP放错了位置导致404或者jar包放在了别的位置导致运行时报ClassNotFoundException。标准结构是这样CIMS/ ├── src/ │ ├── com/cims/bean/ // 实体类User, Course, Enrollment │ ├── com/cims/dao/ // 数据访问类UserDao, CourseDao, EnrollmentDao │ ├── com/cims/servlet/ // 控制器LoginServlet, CourseServlet, EnrollmentServlet │ ├── com/cims/filter/ // 过滤器LoginFilter, EncodingFilter │ └── com/cims/util/ // 工具类DBUtil ├── WebContent/ │ ├── WEB-INF/ │ │ ├── lib/ // mysql-connector-java-8.0.x.jar │ │ └── web.xml // Servlet映射与欢迎页面配置 │ ├── css/ // 静态样式可选 │ ├── login.jsp // 登录页 │ ├── index.jsp // 首页/课程列表 │ ├── course_add.jsp // 添加课程页面 │ └── course_edit.jsp // 修改课程页面 └── sql/ └── cims.sql // 建库建表脚本URL映射规划上我习惯用“资源名即路径”的方式。登录用/loginServlet课程列表用/courseServlet?actionlist添加课程用/courseServlet?actionadd。这种用一个Servlet接收多个action参数的风格适合课程设计因为文件数量少每个Servlet里用switch区分动作比每个功能建一个Servlet更容易讲解。提示如果用的是Eclipse Dynamic Web ProjectJava文件的路径写入到src下如果用的是IDEA默认是src/main/java和src/main/webapp。两种工程的源码结构等价但导入时不要搞混。3. 把CIMS的核心功能拆成三层建表、DBUtil、登录与权限3.1 数据库设计用户、课程、选课三张表的字段与关系CIMS系统不论偏制造还是偏教学管理数据库核心就三张表用户表、课程表、选课关系表。设计时注意“不要为了关系而关系”——课程设计阶段把字段控制在6到8个以内反而更容易把SQL写清楚。-- 用户表区分管理员和普通用户 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role CHAR(1) DEFAULT 1, -- 0管理员, 1普通用户 realname VARCHAR(30), -- 真实姓名用于页面展示 created_at DATETIME DEFAULT NOW() ); -- 课程表CIMS里的核心业务对象 CREATE TABLE t_course ( id INT PRIMARY KEY AUTO_INCREMENT, course_no VARCHAR(20) NOT NULL UNIQUE, -- 课程编号 course_name VARCHAR(50) NOT NULL, -- 课程名称 credit DECIMAL(3,1) DEFAULT 2.0, -- 学分 teacher VARCHAR(30), -- 授课教师 capacity INT DEFAULT 50, -- 课程容量 selected_count INT DEFAULT 0, -- 已选人数 remark VARCHAR(200) -- 备注 ); -- 选课表学生和课程的多对多关系 CREATE TABLE t_enrollment ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, course_id INT NOT NULL, enroll_time DATETIME DEFAULT NOW(), UNIQUE KEY uk_user_course (user_id, course_id), CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES t_user(id), CONSTRAINT fk_course FOREIGN KEY (course_id) REFERENCES t_course(id) );建表脚本里的关键设计点在两处一是t_user.role用字符型CHAR(1)而不是INT因为角色在JSP页面上显示时直接就能用“0是管理员、1是普通用户”来解释不用再做类型转换二是t_enrollment表加了联合唯一约束uk_user_course防止同一个人重复选同一门课。这个约束在代码层面也有判断但数据库约束是最后一道防线缺了它就算代码有漏洞也不会产生脏数据。selected_count这个字段是冗余设计。选课时执行UPDATE t_course SET selected_count selected_count 1 WHERE id ?来维护它。有人会问为什么不直接SELECT COUNT(*) FROM t_enrollment统计因为课程列表页每次加载都要显示每门课的已选人数如果用聚合查询一次列表加载就要执行N1次子查询对课程设计规模的系统虽然影响不大但答辩时讲“用冗余字段避免N1查询”是一个很好的加分点。3.2 JDBC工具类PreparedStatement 连接关闭的细节DBUtil是整套JSP源码里最不该写错的文件。它有两种常见写法一种是用DriverManager手动获取连接另一种是用DataSource连接池。课程设计项目不要用连接池因为Tomcat配置数据源需要改context.xml答辩环境里配置文件一旦出错整个项目起不来排查难度大。手动获取连接虽然每次请求都建立物理连接但CIMS这种并发量个位数的系统根本感觉不到差异。package com.cims.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * 数据库连接工具类 * 课程设计项目采用DriverManager方式简化部署步骤 */ public class DBUtil { // 按自己的MySQL版本调整驱动类名 private static final String DRIVER com.mysql.cj.jdbc.Driver; // MySQL 8.0以上版本需要带serverTimezone参数 private static final String URL jdbc:mysql://localhost:3306/cims?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai; private static final String USERNAME root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(MySQL驱动加载失败请检查WEB-INF/lib下的jar包); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } /** * 关闭资源ResultSet - PreparedStatement - Connection * 关闭顺序必须从里到外反着来 */ public static void close(ResultSet rs, PreparedStatement ps, Connection conn) { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) {} } if (ps ! null) { try { ps.close(); } catch (SQLException ignored) {} } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }这个工具类里有三个细节是答辩时老师常追问的。第一Class.forName(DRIVER)在JDBC 4.0以后其实可以省略因为驱动包里的META-INF/services/java.sql.Driver文件会自动注册驱动但保留它没有任何坏处反而能提前暴露“jar包没放对位置”的问题。第二URL里的serverTimezoneAsia/Shanghai是MySQL 8.0的必填参数不写会报时区错误这是课程设计源码里最常见的运行时报错之一。第三close方法把SQLException吞掉是故意的因为关闭阶段的异常对业务流程没有影响打印堆栈反而会让控制台信息混乱。3.3 登录流程与Session管理从login.jsp到LoginServlet登录是验证整条请求链路是否打通的第一关也是CIMS源码中最能体现“JSPServlet协作”的部分。登录页面的表单提交到LoginServletServlet负责校验用户名密码成功后把用户信息放进Session再转发到首页。%-- login.jsp 登录页面核心部分 --% form action${pageContext.request.contextPath}/loginServlet methodpost div label用户名/label input typetext nameusername required / /div div label密码/label input typepassword namepassword required / /div div label角色/label select namerole option value0管理员/option option value1普通用户/option /select /div button typesubmit登 录/button p stylecolor: red;${requestScope.msg}/p /form这里用${pageContext.request.contextPath}来拼表单提交路径。很多初学者直接写actionloginServlet如果项目部署在根路径下没问题但如果部署后路径变成了http://localhost:8080/CIMS/loginServlet不带项目名的写法就会404。pageContext.request.contextPath是EL表达式里的标准写法用来动态获取项目根路径。${requestScope.msg}用来回显登录失败的提示信息这是JSP里最基础的数据回显。package com.cims.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import com.cims.bean.User; import com.cims.dao.UserDao; WebServlet(/loginServlet) public class LoginServlet extends HttpServlet { private static final long serialVersionUID 1L; private UserDao userDao new UserDao(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 设置请求编码解决POST提交中文乱码 request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); String role request.getParameter(role); // 2. 基本非空校验数据库层还会做二次校验 if (username null || password null || username.trim().isEmpty()) { request.setAttribute(msg, 用户名和密码不能为空); request.getRequestDispatcher(/login.jsp).forward(request, response); return; } // 3. 调用DAO层查询并校验密码 User user userDao.findByUsername(username); if (user ! null user.getPassword().equals(password) role.equals(user.getRole())) { // 4. 登录成功把用户对象存入Session不存密码字段以外的敏感数据 HttpSession session request.getSession(); session.setAttribute(loginUser, user); session.setMaxInactiveInterval(30 * 60); // 30分钟超时 response.sendRedirect(request.getContextPath() /courseServlet?actionlist); } else { // 5. 登录失败回到登录页并回显错误信息 request.setAttribute(msg, 用户名、密码或角色不匹配); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }这段代码有四个面试级细节。request.setCharacterEncoding(UTF-8)只对POST请求有效GET请求的编码是Tomcat在server.xml里控制的这是很多人中文乱码的根源。登录成功后用sendRedirect而不是forward原因是转发浏览器地址栏不变用户刷新页面会再次提交表单造成重复登录重定向则是重新发起一次GET请求。session.setMaxInactiveInterval是Session超时时间不设置的话默认是Tomcat配置的30分钟这里显式设置是为了在答辩时能说出“我控制了Session的有效期”。密码校验用字符串equals是课程设计够用的方案但要在代码注释或答辩时说明“实际生产环境必须用加盐哈希存储密码”。4. JSP页面与Servlet的协作表单提交、数据回显与列表循环4.1 用request.getParameter收集表单数据为什么不用表达式语言也能跑课程设计源码里最容易写“飘”的部分就是JSP页面的取值方式。很多教程会教你在JSP里写${param.xxx}取表单参数但在Servlet里必须用request.getParameter(xxx)。这个区分搞不清页面提交的数据在Servlet里取到的是null。Servlet接收表单数据的标准做法是写在doPost里。如果在doGet里接收POST表单数据Tomcat不会报错但request.getParameter取回来的永远是null。实现登录、添加课程、提交选课这些功能时表单的methodpost和Servlet的doPost方法必须配对。// CourseServlet 中添加课程的入口 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String action request.getParameter(action); // 同一个Servlet根据action参数分发到不同处理方法 if (add.equals(action)) { addCourse(request, response); } else if (update.equals(action)) { updateCourse(request, response); } else if (delete.equals(action)) { deleteCourse(request, response); } else { response.sendRedirect(request.getContextPath() /courseServlet?actionlist); } } private void addCourse(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 从请求中收集表单字段 String courseNo request.getParameter(courseNo); String courseName request.getParameter(courseName); String credit request.getParameter(credit); String teacher request.getParameter(teacher); String capacity request.getParameter(capacity); // 参数校验空值和非法数字检查 if (courseNo null || courseName null || courseNo.trim().isEmpty() || courseName.trim().isEmpty()) { request.setAttribute(msg, 课程编号和课程名称不能为空); request.getRequestDispatcher(/course_add.jsp).forward(request, response); return; } // 这里应该有BigDecimal和Integer转换的try-catch课程设计可以简化为 // 把转换失败后的异常信息直接作为错误提示返回 Course course new Course(); course.setCourseNo(courseNo); course.setCourseName(courseName); course.setCredit(Double.parseDouble(credit)); course.setTeacher(teacher); course.setCapacity(Integer.parseInt(capacity)); boolean ok courseDao.add(course); if (ok) { // 添加成功重定向到列表页避免F5重复提交 response.sendRedirect(request.getContextPath() /courseServlet?actionlist); } else { // 添加失败把用户填了一半的数据回显到表单 request.setAttribute(course, course); request.setAttribute(msg, 课程编号已存在添加失败); request.getRequestDispatcher(/course_add.jsp).forward(request, response); } }这个Servlet里我最想强调的是“添加失败后的数据回显”。很多源码在添加失败后直接跳到course_add.jsp用户填写的内容全丢了体验极差。正确做法是把Course对象放进request.setAttribute(course, course)然后在JSP页面里用${course.courseNo}回填到input标签的value属性里。这体现了Servlet和JSP各自职责的划分Servlet负责处理数据JSP负责把数据展示出来。4.2 JSP列表页面的for循环输出out.print和 配合的写法课程列表页是CIMS系统的门面也是JSP源码中scriptlet代码最密集的地方。课程设计阶段不要求用JSTL标签库用% for(...) %的传统scriptlet写法反而更容易让新手理解循环与页面的关系。但要注意一点JSP页面里不要出现业务逻辑比如计算库存剩余量、判断某个用户是否已选课这些应该在Servlet里准备好JSP只负责显示。%-- course_list.jsp 课程列表核心循环 --% % page importjava.util.List % % page importcom.cims.bean.Course % % page importcom.cims.bean.User % % // 从Servlet转发的request中获取列表数据 ListCourse courseList (ListCourse) request.getAttribute(courseList); User loginUser (User) session.getAttribute(loginUser); if (courseList null) { response.sendRedirect(request.getContextPath() /courseServlet?actionlist); return; } % table border1 cellspacing0 cellpadding6 tr th课程编号/th th课程名称/th th学分/th th教师/th th容量/th th已选/th th操作/th /tr % for (Course c : courseList) { % tr td% c.getCourseNo() %/td td% c.getCourseName() %/td td% c.getCredit() %/td td% c.getTeacher() %/td td% c.getCapacity() %/td td% c.getSelectedCount() %/td td a href% request.getContextPath() %/courseServlet?actioneditid% c.getId() %修改/a a href% request.getContextPath() %/courseServlet?actiondeleteid% c.getId() % onclickreturn confirm(确定删除该课程)删除/a /td /tr % } % /table这段代码有三处容易被新手改坏的细节。第一处是页面顶部的指令% page import... %JSP页面用到Java类就必须导入漏了会直接报编译错。第二处是列表页开头对courseList的判空处理这个判断的意义在于如果用户没有请求courseServlet?actionlist直接访问course_list.jsp页面会重定向到正确入口防止NullPointerException。第三处是onclickreturn confirm(...)这是前端最简单的防误删手段删除操作属于不可逆操作必须加二次确认。tr标签被% for %和% } %包住的形式看起来有点丑但这正是JSP原始语法的特点。大多数课程设计项目为了控制在“让老师能看懂”的范围内都会使用这种写法。如果你愿意引入JSTL的c:forEach会让页面干净很多但这需要在WEB-INF/lib里多放两个jstl的jar包在不熟悉的项目里不推荐。4.3 Filter统一做登录校验让每个页面不再重复写Session检查CIMS系统里有登录页、课程列表页、课程编辑页、选课页等多个页面如果不加拦截用户直接在浏览器地址栏输入courseServlet?actionlist就能跳过登录访问。最LOW的解决方案是每个Servlet开头都复制粘贴一段Session判断代码但更好的做法是用一个Filter统一拦截这也是源码质量评分的分水岭。package com.cims.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * 登录过滤器拦截所有请求除了login.jsp和loginServlet */ WebFilter(/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; // 获取请求路径 String uri request.getRequestURI(); String contextPath request.getContextPath(); String path uri.substring(contextPath.length()); // 白名单这些路径无需登录即可访问 boolean isAllowed path.equals(/login.jsp) || path.equals(/loginServlet) || path.startsWith(/css/) || path.endsWith(.js) || path.endsWith(.png) || path.endsWith(.gif) || path.endsWith(.jpg) || path.endsWith(.ico); if (isAllowed) { chain.doFilter(req, resp); return; } // 非白名单请求检查Session中是否有登录用户 HttpSession session request.getSession(false); // 不创建新Session Object loginUser (session ! null) ? session.getAttribute(loginUser) : null; if (loginUser null) { // 未登录跳转到登录页 response.sendRedirect(contextPath /login.jsp); return; } // 已登录放行请求 chain.doFilter(req, resp); } public void init(FilterConfig config) throws ServletException { } public void destroy() { } }这个Filter解决的是“哪些请求不需要登录”和“哪些请求必须登录”的边界问题。白名单的设计很关键因为登录页本身、登录的Servlet、CSS和图片这些静态资源不能拦截否则会出现“登录页面加载不出来样式”或者“登录请求被拦截”的死循环。request.getSession(false)这个写法是第二个细节false参数表示“如果当前没有Session就返回null而不是创建一个”如果不加false未登录用户访问任意页面都会触发Session创建每个请求都会生成一个无用的Session对象白白占用服务器内存。5. 课程设计源码的避坑手册从部署到验收的五个典型问题5.1 页面404WEB-INF下的JSP不能直接URL访问现象项目部署后访问首页正常但从列表页点击“登录”跳转时报404地址栏输入http://localhost:8080/CIMS/WEB-INF/login.jsp也打不开页面。原因WEB-INF目录是Servlet容器的受保护目录容器禁止客户端直接访问该目录下的任何文件。这是Servlet规范的安全约定。很多学生习惯把JSP页面全放进WEB-INF里防止被直接访问但同时也把自己坑了——如果web.xml或Servlet转发路径写错页面永远无法通过URL直接访问验证。解决课程设计方案中把login.jsp、index.jsp这类允许直接访问的JSP放在webapp根目录下把不允许直接访问的JSP片段被include的header.jsp、footer.jsp、错误页放在WEB-INF下。在Servlet中使用request.getRequestDispatcher(/WEB-INF/course_edit.jsp).forward(request, response)可以合法访问到WEB-INF下的文件但直接输入URL不行。5.2 中文乱码POST提交乱码和GET提交乱码的两种解法现象添加课程时填写中文课程名称保存到数据库后显示“???”或者从数据库查出来在页面上显示为乱码登录时用户名为中文也会乱码。原因POST请求乱码是请求体编码不是UTF-8GET请求乱码是URL参数编码由Tomcat的URIEncoding控制。很多学生只执行request.setCharacterEncoding(UTF-8)就以为万事大吉遇到GET请求乱码就彻底懵了。解决POST乱码的解法是在Servlet的doPost方法第一行加request.setCharacterEncoding(UTF-8)GET乱码则需要改Tomcat的conf/server.xml在Connector标签里加URIEncodingUTF-8。注意过滤器Filter里同样可以统一设置请求编码正确顺序是EncodingFilter先执行设置编码再执行LoginFilter判断登录状态。如果两个Filter的顺序反了LoginFilter里如果读取了参数值就会读到乱码。提示数据库端也需要统一编码。连接URL里的characterEncodingutf8和MySQL默认字符集都要检查否则后端都是UTF-8MySQL端还是latin1数据照样存成乱码。5.3 MySQL 8.0驱动报ClassNotFound / SSL警告现象Tomcat启动时项目正常但第一次访问数据库相关页面时白屏控制台报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver或者项目能跑但控制台刷SSL connection error警告。原因com.mysql.jdbc.Driver是MySQL 5.x的驱动类名MySQL 8.x驱动改名为com.mysql.cj.jdbc.Driver。项目里某个或多个Class.forName还是旧类名驱动不能加载。SSL警告是MySQL 8.0连接URL没有显式禁用或用serverTimezone导致的时区与SSL协商问题本身不是致命错误但会干扰排错时看日志。解决统一替换所有Class.forName和JDBC连接URL里的驱动路径com.mysql.cj.jdbc.DriverURL加serverTimezoneAsia/ShanghaiuseSSLfalse。如果用的是Maven项目检查pom.xml中的mysql-connector-java版本是否以8.0.x开头。还有一点容易被忽略多个版本的MySQL驱动jar包一起放在WEB-INF/lib下会冲突只保留一个。5.4 表单重复提交点F5就重复插入一条选课记录现象学生选课成功后点击浏览器刷新按钮弹出“是否重新发送表单”的提示确认后数据库里出现了同一条重复选课记录。虽然数据库有唯一约束会拒绝第二次插入但添加课程这种没有唯一约束的数据刷几次就有几条一样的数据。原因添加操作用的是response.sendRedirect还好但如果实现时用了forward浏览器地址栏还是courseServlet?actionadd这个POST请求用户点击刷新时浏览器会重新发送上一次的POST请求造成重复提交。解决遵循PRG原则Post/Redirect/Get表单提交成功后一律用response.sendRedirect跳转到列表页不要用forward。如果希望做更严格的防重复可以在表单里生成一个一次性token存到Session中提交时校验并从Session中移除该token。课程设计项目中做到PRG就足够应付答辩了。5.5 Tomcat 10与javax.servlet包名不一致现象源码在自己电脑的Tomcat 9上能跑拿到实验室的Tomcat 10后启动报错java.lang.NoClassDefFoundError: javax/servlet/ServletException。原因Tomcat 10把Java EE的包名从javax.servlet迁移到jakarta.servlet向下不兼容。这是Servlet规范从Java EE转向Jakarta EE后的重大变化。写于2020年前的课程设计源码几乎全部基于旧包名直接放到Tomcat 10跑必然报错。解决课程设计源码统一按Tomcat 8.5/9.0来写。如果被迫用Tomcat 10要么批量替换所有Java文件和JSP页面里的javax.servlet为jakarta.servlet要么换回Tomcat 9。在答辩时这个问题的标准回答是“我用的是Tomcat 9稳定版因为Tomcat 10引入了Jakarta命名空间旧代码不兼容。”6. 让源码在验收时更好讲验证清单与三处可拔高的改造点源码能跑只是及格线课程设计要拿高分关键是你能否把系统讲出“设计感”。我在验收CIMS这类项目时最看重的是学生能不能回答三个问题权限是怎么控制的、数据库表是怎么关联的、如果数据量增长到一万条哪些地方会变慢。把这三点准备好比堆代码量有效得多。验证清单第一项是功能闭环测试。把管理员、普通用户两种角色分别建好走一遍完整流程登录、添加课程、列表显示、修改课程、退选课程。每一步都要截图保留。第二项是异常路径测试故意输入错误密码、直接访问未授权的JSP页面、删除已经被选课的课程——这些场景提前验证好答辩时被问到“系统崩溃怎么办”才能答得上来。值得动手改造的第一个点是课程列表分页。目前5.2里的列表是SELECT * FROM t_course一次性查出全部数据课程数量在几十条时没问题但答辩时肯定会问“课程有500条怎么办”。用LIMIT offset, pageSize加一个previousPage/nextPage的按钮代码量增加约30行讲解时的加分效果远高于30行代码本身。第二处是选课前的人数校验。课程有capacity和selected_count字段选课逻辑里应该加一个判断如果selected_count capacity提示“课程已满员”阻止选课。这个逻辑放在EnrollmentServlet的doPost里执行用UPDATE t_course SET selected_count selected_count 1 WHERE id ? AND selected_count capacity这种带条件更新的SQL比先查后更更安全。Redis或乐观锁这类聊法是加分项但课程设计答辩不深究也问题不大。第三处是日志记录。用一个LogFilter把每次请求的访问时间、操作人、IP地址打到控制台或数据库里。代码量不多但要讲清楚“我可以从日志里追溯谁在什么时间做了什么操作”这是CIMS系统从CRUD演示升级为带管理思维的最直观证据。多年的课程设计带教经验下来我最想跟你说的一个习惯是源码交付前重命名好项目的工程名比如CIMS_你的学号把所有注释里出现作者的位置都改掉确保换一台电脑按说明步骤能跑通。我见过太多翻车现场是换了电脑跑不起来然后当众改代码。项目能独立跑通、每一项功能都能按路径讲出缘由这套源码才算真正完成了它的使命。希望帮到你。本文还有配套的精品资源点击获取
返回列表