JSP+Servlet+MySQL教务管理系统源码:从环境部署到事务改造实战
简介这是一份基于 JSPServletMySQL 构建的教务管理系统完整源码适合 Java Web 初学者、课程设计或毕业设计选题者用于理解传统 SSM 之外的原生 MVC 分层开发思路。项目覆盖学生信息、教师资料、课程与考试安排等核心模块包含完整业务逻辑与数据库脚本。压缩包共 530 个文件约 9.32MB以 jsp 页面、java 源码、class 编译文件、sql 脚本及 jar 依赖库为主另含 gif、jpg 等界面素材与 swf、js、css、xml 配置资源目录结构清晰便于对照学习。已有 719 人学习浏览。代码中可看到 UserManager、LoginAction、ModifyUserAction 等功能类配合 JSTL、EL 表达式、C3P0 连接池及 MVC 设计模式演示了从表单请求、Servlet 业务处理到 MySQL 数据持久化的完整链路是一份能直接运行、适合拆解研读的实战型教学项目。1. JSPServletMySQL 教务管理系统为什么这本老源码今天还有人翻又到毕业设计季我看到不少人在翻 JSPServletMySQL 的教务管理系统源码。这组合听起来老但它把登录认证、角色权限、选课事务这些 Java Web 核心问题完整串了一遍是少数能让你站在答辩台上把每一行代码都讲清楚的项目。适合两类人拿它做课程设计底子的学生和要接手学校遗留系统的开发者。前者求跑通后者求看懂。这篇按配环境、导数据库、跑通选课、权限控制、排坑的顺序把这套老源码从头拆到尾。2. 先配环境JDK、Tomcat、MySQL 的版本搭配与部署前准备2.1 版本搭配老项目为什么对 Tomcat 和 JDK 这么挑剔JSP 源码最常见的问题不是代码本身而是环境不匹配。这套教务系统如果是 2015 到 2018 年之间的产物代码里 import 的是 javax.servlet.*用的是 JSP 2.x 规范。你要是拿 Tomcat 10 来跑启动时直接报错——Tomcat 10 把包名从 javax 换成了 jakarta老代码里import javax.servlet.http.HttpServlet全部失效。所以第一原则是老项目不要用新容器。JDK 8 配 Tomcat 8.5 或 9.0 是最稳的搭配JDK 也不要往 11、17 上凑老代码里有些反射操作在更高 JDK 上会出现莫名的访问限制错误。连接 MySQL 这一侧同样敏感。很多这套源码配套的 SQL 脚本是按 MySQL 5.x 写的表结构用 ENGINEInnoDB DEFAULT CHARSETutf8mb4 没问题但驱动是个坎。mysql-connector-java 5.1.x 连 MySQL 8.x 会碰到认证插件不兼容具体报错后面有专门一节讲。如果你现在要新装数据库我一般建议直接装 MySQL 5.7.44这是 5.7 系列的收尾版本之后 5.7 分支不再发新版也正是这类 JSP 项目最常见的运行环境。想用 MySQL 8.x 的话驱动必须换成 8.0.x 的 connector连接 URL 里还要额外加 allowPublicKeyRetrievaltrue。先确定三样环境Windows 下打开命令行验证# 确认 JDK 版本8 和 8u201 之前的老版本建议升级 java -version # 确认 JAVA_HOME注意指向 JDK 根目录而不是 bin echo %JAVA_HOME% # 确认 Tomcat 版本启动前先看版本信息 catalina.bat version没设 JAVA_HOME 时Tomcat 的 startup.bat 会直接失败窗口一闪而过。我见过不少同事只装了 JRE 就以为完事实际上老版本 Tomcat 启动要依赖 JDK 里的编译器工具只装 JRE 同样跑不起来。下面是一张我按这套源码总结的版本参照表照着配能少走弯路组件推荐版本说明JDK8u201 以上兼容旧编译代码启动快Tomcat8.5.x 或 9.0.x仍是 javax 包名规范MySQL5.7.44老 SQL 脚本兼容性最好mysql-connector-java5.1.49配 MySQL 5.7认证插件不冲突2.2 从 zip 到能跑解压、目录结构识别与部署方式拿到 zip 压缩包先别急着双击解压到桌面。先用压缩软件打开看一眼目录结构。这类源码常见的组织方式有两种一种是带 .project 和 .classpath 的 Eclipse 工程直接用 IDE 导入再用另一种是已经打好的 Web 应用目录里面有 WEB-INF 和一堆 jsp 页面这种可以直接拷进 Tomcat 的 webapps 目录。你解压后看到的东西和下面这个结构应该大差不差edu_mis/ ├─ WEB-INF/ │ ├─ web.xml # Servlet 映射、Session 超时与欢迎页配置 │ ├─ lib/ # JDBC 驱动、JSTL 标签库的 jar 文件 │ └─ classes/ # 编译后的 Servlet 字节码 ├─ css/ js/ images/ ├─ login.jsp ├─ index.jsp └─ student/ # 学生端页面 ├─ course_list.jsp └─ select_course.jspWEB-INF 里的内容浏览器不能直接访问这是 Servlet 规范的保护机制。所以教务系统的 JSP 才按角色分包放在不同目录登录后跳转到各自的首页。如果你用 IDEA 导入注意把 lib 下的 jar 添加为 Library把 classes 设为编译输出目录直接拷贝部署就省事多了整个 edu_mis 目录放进 webapps启动 Tomcat访问http://localhost:8080/edu_mis/login.jsp就能看到登录页。打开 web.xml 时有三个配置值得多看一眼。第一个是 servlet-mapping它决定/login、/selectCourse这些路径分别由哪个 Servlet 处理表单提交路径和映射对不上就会 404第二个是 session-config 里的超时时间默认 30 分钟第三个是 welcome-file-list访问根路径时先进哪个页面。改错任何一个项目都转不起来。2.3 数据库准备SQL 脚本导入与连接账号处理把脚本导入 MySQL 是跑通整个项目前的最后一步。常见做法是在命令行里执行Windows 下打开 cmd先登录再 source# 登录 MySQL输入密码进入交互模式 mysql -uroot -p # 进入 MySQL 后执行脚本路径用正斜杠或双反斜杠 source C:/path/to/edu_mis.sql;或者直接重定向导入注意 -p 后面紧跟密码没有空格mysql -uroot -p123456 C:/path/to/edu_mis.sql这里有个高频问题SQL 脚本用 Windows 记事本打开过包含中文注释导入时报语法错误或者注释乱码。原因是脚本文件被存成了 GBK 编码。解决办法是把脚本另存为 UTF-8 编码再重新导入。导入完成后进 MySQL 执行SHOW TABLES;看到 user、course、student_course 这些表就说明建库成功。接下来处理连接账号。绝大多数这类源码的数据库连接信息写在 DBUtil.java 或 db.properties 里默认是 root/123456 之类。你本地 MySQL 的 root 密码大概率不是这个所以要改成本地密码或者建一个匹配的专用账号。我一般会单独建一个账号不让 root 直接出现在代码里——课程设计可以偷懒但接手遗留系统时这个习惯能省掉不少麻烦。3. 跑通第一个选课流程从数据库表到 Servlet 后端逻辑3.1 表结构拆解user、course、student_course 三张表的关系教务管理系统的数据模型核心就三张表。user 表存的是管理员、教师、学生三类账号用一个 role 字段区分好处是登录逻辑只查这一张表不用分开写三套验证。course 表存课程基本信息和容量其中 capacity 是容量selected 是已选人数。student_course 是选课关系表记录谁选了哪门课、考了多少分。这个模型很典型答辩时老师最常问的就是为什么把成绩放在关系表上而不是课程表里——因为一名学生一门课只有一条成绩记录放课程表里会存不下所有学生的成绩。选课关系表一定要有联合唯一键也就是 UNIQUE KEY uk_student_course(student_id, course_id)从数据库层面杜绝重复选课。这比在 Java 代码里先查再插更可靠两个操作之间存在时间窗口并发时照样会插重。一个完整的建表脚本大概长这样CREATE DATABASE IF NOT EXISTS edu_mis DEFAULT CHARACTER SET utf8mb4; USE edu_mis; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(50), role CHAR(1) DEFAULT 3 COMMENT 1管理员 2教师 3学生, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, teacher_id INT, capacity INT DEFAULT 50, selected INT DEFAULT 0, CONSTRAINT fk_course_teacher FOREIGN KEY (teacher_id) REFERENCES user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE student_course ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2) DEFAULT NULL, select_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_student_course (student_id, course_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;几个参数要能讲清楚user 表同时容纳教师和学生这叫单表继承角色靠 role 区分是这类小项目最常见的做法course 表把 teacher_id 设为外键指向 user表示开课教师student_course 不设外键也行但联合唯一键必须有。外键在清空重导时容易卡住比如你先 DELETE 再 INSERT 会因外键约束被拒绝顺序要反过来。3.2 选课核心链路DAO 层手动控制事务选课功能综合了整份源码里最难的一块——事务。它要做四件事查课程是否已满、查学生是否重复选课、插入选课记录、更新已选人数。几步必须作为整体成功或失败否则会出现「选课记录插进去了但人数没加一」这种脏数据。这就要把事务边界划清楚。教学风格的选课 DAO 方法常见写法如下public boolean selectCourse(int studentId, int courseId) { Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交手动控制事务 // 1. 锁住课程行防止并发超选 String lockSql SELECT capacity, selected FROM course WHERE id ? FOR UPDATE; ps conn.prepareStatement(lockSql); ps.setInt(1, courseId); rs ps.executeQuery(); if (!rs.next()) return false; // 课程不存在 int capacity rs.getInt(capacity); int selected rs.getInt(selected); if (selected capacity) { throw new SQLException(课程已满); } // 2. 检查是否重复选课 String checkSql SELECT COUNT(*) FROM student_course WHERE student_id ? AND course_id ?; ps conn.prepareStatement(checkSql); ps.setInt(1, studentId); ps.setInt(2, courseId); rs ps.executeQuery(); if (rs.next() rs.getInt(1) 0) { throw new SQLException(已选过该课程); } // 3. 插入选课记录 String insertSql INSERT INTO student_course(student_id, course_id, select_time) VALUES(?, ?, NOW()); ps conn.prepareStatement(insertSql); ps.setInt(1, studentId); ps.setInt(2, courseId); ps.executeUpdate(); // 4. 更新已选人数 String updateSql UPDATE course SET selected selected 1 WHERE id ?; ps conn.prepareStatement(updateSql); ps.setInt(1, courseId); ps.executeUpdate(); conn.commit(); // 全部成功后统一提交 return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } finally { try { if (conn ! null) conn.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } } }这段代码里三个参数值得琢磨setAutoCommit(false) 关闭自动提交后面所有语句都在一个事务里explicit 调用 commit 或 rollback 才真正落库FOR UPDATE是行级锁两个学生同时选同一门课后到的会被阻塞到前一个提交或回滚避免容量被超卖finally 里把 autoCommit 恢复为 true是给连接池做个清理不然连接复用后状态是脏的下一次拿着事务未提交的连接去查数据会读到旧快照。这个方案用 SELECT FOR UPDATE 锁行高并发下性能一般但作为课程设计完全够用。你能把这些话讲清楚老师基本不再追问。想改好的话方向是把锁粒度放细或者用 UPDATE course SET selected selected 1 WHERE id ? AND selected capacity 这种条件更新这是后话。3.3 从 Servlet 到 JSPrequest 作用域与转发的配合选课完成后要把结果反馈给页面这一步就牵扯到 Servlet 和 JSP 之间的数据传递。常见做法是用 request.setAttribute 把消息放进请求作用域然后 request.getRequestDispatcher(...).forward(...) 做服务端转发而不是 sendRedirect。两者区别很大转发是服务端内部跳转request 里的数据原样带到目标页重定向是浏览器重新发起一次全新请求request 里的数据全丢。你如果用重定向再想取 message取到的一定是 null。一个选课 Servlet 的典型片段WebServlet(/selectCourse) public class SelectCourseServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 从 Session 拿当前登录学生 User user (User) req.getSession().getAttribute(currentUser); int courseId Integer.parseInt(req.getParameter(courseId)); CourseDao dao new CourseDao(); boolean ok dao.selectCourse(user.getId(), courseId); // 把结果放进 request转发给课程列表页 req.setAttribute(message, ok ? 选课成功 : 选课失败课程已满或已选过); req.getRequestDispatcher(student/course_list.jsp).forward(req, resp); } }这里两个容易翻车的点。第一parseInt 直接取参数时如果 courseId 传的是空字符串就抛 NumberFormatException项目里不一定处理你可以顺手加上 try-catch给一个友好提示再转发回去。第二课程列表页大多用 JSTL 的 c:forEach 循环渲染表格前提是 lib 里有 jstl.jar 和 standard.jar没放齐的话页面直接报The absolute uri cannot be resolved这是 JSP 项目里最常见的白屏原因之一。另外列表查询的 SQL 通常按ORDER BY select_time DESC排序让新选的课排前面这个细节在页面展示顺序不对时优先检查。4. 登录认证的完整链路LoginServlet、Session 与过滤器时机4.1 登录验证放哪Servlet 判断和过滤器拦截各管一段登录逻辑看着简单写法却分两派。常见做法是登录请求交给一个 LoginServlet它查数据库比对用户名和密码成功就把用户对象放进 Session其他页面访问权限交给一个过滤器统一拦截没登录就踢回登录页。这样职责划分清楚也是这套源码通常采用的方案。LoginServlet 的核心逻辑大概是这样WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 不设编码的话中文用户名会乱码这一行必须在读参数之前 req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); // 源码里多数是 MD5 后比对明文比对的也有不建议学 UserDao userDao new UserDao(); User user userDao.findByUsernameAndPassword(username, password); if (user null) { req.setAttribute(errorMsg, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); return; } // 登录成功用户对象和角色都塞进 Session HttpSession session req.getSession(true); session.setAttribute(currentUser, user); session.setAttribute(role, user.getRole()); // 按角色分流管理员、教师和学生进不同首页 String target; if (1.equals(user.getRole())) { target admin/index.jsp; } else if (2.equals(user.getRole())) { target teacher/index.jsp; } else { target student/index.jsp; } resp.sendRedirect(req.getContextPath() / target); } }这段代码说明三件事setCharacterEncoding 必须在读取参数之前调用放后面就失效这是乱码问题的重灾区密码一般做一次哈希MD5 在老源码里见得最多安全强度不高但比明文好一点角色分流决定后面过滤器要怎么设计放行规则。你写完这段要能回答老师一个问题密码能不能再加固能下面第 6 章会讲。4.2 Session 里存什么对象加角色别只存一个登录标记登录成功以后Session 里至少要放两样东西用户对象和角色。用户对象里有当前登录人的 id、姓名选课功能要知道当前学生是谁成绩录入要知道当前教师是谁这些都在页面上频繁取用角色字段用于功能入口的显示和权限判断比如学生登录后只显示选课和查成绩菜单教师登录后显示录入成绩菜单。Session 超时时间通常由 web.xml 配置默认 30 分钟。教务系统操作频率不高30 分钟基本够用但录成绩这种慢节奏操作容易被踢回登录页在线时长不够体验很差。常见做法是把超时调长一点session-config session-timeout60/session-timeout /session-config这个配置的单位是分钟别填大数字填 60 就是 60 分钟。还有一个反直觉的点必须记住request.getSession() 会在没有会话时创建一个新 Session而 getSession(false) 只在已有会话时返回。过滤器里判断是否登录必须用 getSession(false)否则每个匿名访问都会被系统白送一个 Session没有登录页也会被反复写会话内存压力不必要地变大。4.3 过滤器拦截URL 白名单怎么配才不误伤静态资源过滤器的拦截规则决定了整个系统的访问边界。一套最稳妥的规则是登录页和登录接口放行CSS、JS、图片放行其余全部拦截登录后再根据角色做功能级控制。放行规则配不好会出两个问题一是样式全丢二是登录页自己都被拦截形成死循环。WebFilter(/*) public class AuthFilter 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(); if (uri.endsWith(login.jsp) || uri.contains(/login) || uri.endsWith(.css) || uri.endsWith(.js) || uri.endsWith(.png) || uri.endsWith(.jpg)) { chain.doFilter(req, resp); return; } // 未登录一律踢回登录页 HttpSession session request.getSession(false); if (session null || session.getAttribute(currentUser) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }这个过滤器有两个值得注意的地方。首先是白名单匹配方式uri.endsWith(.css) 只匹配以 .css 结尾的路径如果项目静态资源带版本号比如 style.css?v1这个写法会放行失败样式照样被拦。稳妥做法是改成 uri.contains(.css) 或单独判断资源目录前缀。其次过滤器只做登录态校验不做功能级权限校验。学生登录后手敲 URL 访问 teacher/add_score.jsp 是能进去的页面能不能操作全看 JSP 里有没有再判断一次角色。真要做得严需要在过滤器里维护一张「角色到可访问路径」的映射表这是你能写进论文的加分点。5. 部署与改代码的踩坑清单乱码、驱动、端口这些坎别反复过5.1 登录和数据库全中文乱码三个地方没对齐现象登录界面输入中文用户名后查不到数据数据库里显示成问号JSP 页面标题也花。原因乱码是编码链断裂。JSP 文件本身是 UTF-8但 Servlet 读请求参数时没设编码默认按 ISO-8859-1 解码数据库连接 URL 没指定 characterEncoding表的默认字符集不是 utf8mb4。三个环节只要一个不对中文就花。解决逐一检查。JSP 头部写% page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 %所有读参数的 Servlet 在方法开头加req.setCharacterEncoding(UTF-8)JDBC URL 里加useUnicodetruecharacterEncodingUTF-8不管 MySQL 5.7 还是 8.x 都加。做完这三处乱码基本绝迹。注意 setCharacterEncoding 必须在第一次读取参数前调用放在后面无效是玄学其实是 Servlet 规范规定请求参数只能解码一次。5.2 JDBC 驱动与 MySQL 认证插件不匹配现象用 mysql-connector-java 5.1.x 连本地 MySQL 8.x启动或第一次查询报Public Key Retrieval is not allowed或者直接报Unable to load authentication plugin caching_sha2_password。原因MySQL 8 默认用户认证插件是 caching_sha2_password老驱动 5.1 根本不认识这个插件。解决最省事的是让驱动和服务器同代。MySQL 8 配 mysql-connector-java 8.0.xMySQL 5.7 配 5.1.x。坚持老驱动连新库的话连接 URL 加allowPublicKeyRetrievaltrueuseSSLfalse同时把 MySQL 用户认证方式改回 mysql_native_password。注意这只能用于本机测试生产上改账号插件会影响安全基线不建议。另外 MySQL 8.4 开始默认移除了 mysql_native_password老项目跑 8.4 以上基本只能换驱动。这就是这类 JSP 源码建议锁 MySQL 5.7 的原因。5.3 Tomcat 启动即退出窗口一闪而过没报错现象双击 startup.bat 窗口闪一下就没访问 localhost:8080 直接拒连。原因最常见两类。第一是 JAVA_HOME 没配置Tomcat 启动脚本找不到 JDK第二是 8080 端口被占用Catalina 抛端口冲突异常后直接退出。解决不要在桌面双击 startup.bat改用命令行执行窗口就不会闪。看到报错后去 Tomcat 的 logs 目录翻 catalina.out 或当天日期的 localhost 日志问题定位具体到哪一行。端口占用就改 conf/server.xml 里的Connector port8080 ... /改成 8081 或任意空闲端口然后访问对应端口。JAVA_HOME 没设就加到系统环境变量指向 JDK 安装根目录注意不是 bin 子目录。5.4 404 与 500谁拦了你的请求日志怎么定位现象访问http://localhost:8080/edu_mis/login.jsp返回 404登录后跳转也 404另一种是页面能开但样式全裸。原因404 绝大多数是路径没对齐。部署目录不叫 edu_misURL 和 webapps 下的目录名对不上或者 Servlet 的 WebServlet 路径和表单提交的路径不一致。500 是后端代码运行时异常常见的是空指针或 JDBC 连接失败异常栈里带着具体文件和行号。样式全裸属于过滤器把 CSS 请求也拦了参照 4.3 的白名单排查。解决先确认部署目录名URL 第一段必须和 webapps 下的目录名一致再检查 WEB-INF/classes 下有没有编译出的 .class 文件IDEA 导入时编译输出没配好的话 classes 是空的Servlet 全部 404JSP 报 500 就翻 Tomcat 的 logs/localhost.yyyy-mm-dd.log看栈顶第一行别从下往上找第一行才是真实错误位置。5.5 页面加载后自动刷新一次一个真实会用上的技巧现象被问到频率很高——某个数据显示页希望打开后自动刷新一次把刚生成的数据带出来但不想要定时刷新反复打断操作。原因这类页面常见于数据异步生成的场景比如导入成绩提交后列表页想自动加载最新结果。裸写location.reload()会死循环因为 reload 后页面重新加载又触发 onload再 reload浏览器直接卡死。解决用一次性标记或者用 meta 延迟刷新。两种写法都给你%-- 方式一加 window 标记保证只刷新一次 --% body onloadif(!window._refreshed){window._refreshedtrue;location.reload();} %-- 方式二3 秒后刷新当前页适合等后端处理完成 --% meta http-equivRefresh content3;urlcourse_list.jsp方式一的window._refreshed是一次性开关刷新后内存里的标记保留不会再触发第二次这是防死循环的关键。方式二 content 里的第一个数字是秒数省略 url 部分就刷新当前页面。真实项目我更推荐第二种它给后端处理留了缓冲时间逻辑也直观。这个技巧用到的地方不多但真遇到时没有现成答案值得记下来。6. 从「能跑」到「能用」把连接池、密码和异常提示改扎实跑通只是第一步。这套源码给你的是骨架想让它在答辩或实际维护中站得住我建议按下面这个优先级动手改造每个改动都不大但收益明显。第一个改动是密码存储。源码里明文或 MD5 直接存改法是在注册时用 BCrypt 或 MD5 加盐后存储登录时不再查WHERE password ?而是先按用户名把用户查出来再用工具类比对哈希。这个改动只动 UserDao 和 LoginServlet 两个文件但能在答辩时回答「密码安全怎么考虑」这个必问题。第二个改动是数据库连接。DBUtil 里每次 getConnection 都是 DriverManager 直连换成连接池以后性能和稳定性都上一个台阶。常见做法是引入 HikariCP配置一个文件搞定核心参数是 maximumPoolSize 设 10、minimumIdle 设 5连接池预热在第一次请求时完成之后每次取连接都不再有 TCP 建连开销。替换点只有 DBUtil 一个类Servlet 和 DAO 完全不用动。第三个改动是异常处理。现在 Servlet 里 SQLException 一旦抛出Tomcat 就渲染整页红色异常栈学生能看到 Java 报错这既不好看也不安全。给系统加一个统一异常处理Servlet 里 try-catch 后把错误信息 setAttribute 到一个提示页或者用 web.xml 的 error-page 把 500 请求定向到友好页面。后者配置量最小但前者能保留具体错误内容适合排查问题。第四个改动是 DAO 层去重。源码里每个 DAO 都是重复的写 PreparedStatement、setInt、executeUpdate 套路抽一个 BaseDao把这段模板代码收敛起来新增表时少写一半。这个改造不改变任何功能但代码量直接可见地变薄了是答辩展示工程能力的好素材。我自己的习惯是拿到这类老源码先跑通再按连接池、密码、异常这三项做一轮加固最后才动业务功能。顺序反了容易踩坑——功能改到一半发现底层的数据库连接和异常处理是坏的排查起来两头都乱。这套 JSPServletMySQL 的老架构虽然新项目里用得不多了但把这份代码吃透你再去看 Spring MVC 的处理流程会顺很多因为 Servlet 的请求生命周期、转发与重定向、Session 管理这些底子全在里面。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

Java验证码实现:BufferedImage、Redis与AES加密的点击滑动验证码方案

Java验证码实现:BufferedImage、Redis与AES加密的点击滑动验证码方案

简介:针对网站登录、注册等场景的Java验证码实现方案,覆盖点击文字验证码与拖动/滑动图片验证码两类交互形式,面向需要快速集成或自研行为验证码的后端/全栈开发人员。源码基于JDK1.8、Spring Boot 2.1.17与Redis构建,同时提供dem…

2026/10/7 5:31:09 阅读更多 →
端侧AI部署底层逻辑:从张量布局到NPU性能瓶颈

端侧AI部署底层逻辑:从张量布局到NPU性能瓶颈

做了快十年的端侧 AI 部署工作,有个问题我几乎每隔几天就会遇到一次:同一个模型在 GPU 云主机上跑得飞快,一到手机、开发板、工业盒子上就卡得怀疑人生。问题很少出在模型本身,大概率是底层的执行链路没理顺——从张量&#xff08…

2026/10/7 5:31:09 阅读更多 →
嵌入式分享#63:RK3588 怎么选?全系列选型指南

嵌入式分享#63:RK3588 怎么选?全系列选型指南

嵌入式分享#63:RK3588 怎么选?全系列选型指南 最近几年,RK3588 在嵌入式圈子里热度确实很高。 不管是开发板、边缘计算盒子、NVR、工控机、AI 视觉,还是多屏显示项目,经常都能看到它的身影。 原因也不复杂:…

2026/10/7 5:31:09 阅读更多 →

最新新闻

卡通风格工地临时工作区场景搭建:从关键词拆解到渲染的完整流程

卡通风格工地临时工作区场景搭建:从关键词拆解到渲染的完整流程

1. 从“外景 工地 卡通风格工地 临时工作区”这组词里,我读出了什么第一次看到“外景 工地 卡通风格工地 临时工作区”这组词的时候,我脑子里蹦出来的不是某个具体的软件,而是一整套视觉资产的搭建流程。这组词看起来像是素材库里的标签组合&…

2026/10/7 5:53:25 阅读更多 →
Roo Code 接本地模型卡顿?这份参数优化指南让速度起飞

Roo Code 接本地模型卡顿?这份参数优化指南让速度起飞

如果你现在正在用 Roo Code 接本地模型,大概率遇到过这样的场面:任务刚发出去,状态栏转圈半天,好不容易开始输出了,又一字一顿,像把打字速度调成了 0.5 倍速。我一开始以为是模型选小了,从 14B …

2026/10/7 5:53:25 阅读更多 →
DeepSeek Harness桌面端实测:多Agent工作流编排与内网部署指南

DeepSeek Harness桌面端实测:多Agent工作流编排与内网部署指南

DeepSeek Harness 出桌面端的消息,我是先在几个开发群里看到的,一开始以为是某个第三方套壳,后面顺着线索扒下来才发现是官方把原来的命令行工作流打包成了桌面应用。以前这个工具劝退过不少人,光是环境变量、配置文件、命令行参数…

2026/10/7 5:53:25 阅读更多 →
程序计数器PC搭建全攻略:从74LS161到真实CPU取指逻辑

程序计数器PC搭建全攻略:从74LS161到真实CPU取指逻辑

计算机组成原理这门课,多少人的噩梦是从实验课开始的。特别是“程序计数器(PC)”这个模块,看着书上那几条线和时序图觉得很简单,真上台架一接杜邦线就开始翻车:LED该亮的乱闪,按复位键不灵&…

2026/10/7 5:53:25 阅读更多 →
Orca:面向生产级AI代理协同的并行操作系统

Orca:面向生产级AI代理协同的并行操作系统

1. Orca 是什么:一个被严重低估的 AI 代理协同操作系统Orca 不是一个模型,不是一款聊天应用,更不是某个大厂新推的“AI助手”营销概念。它本质上是一套为多 AI 代理(Multi-Agent)协同工作而设计的运行时环境与调度中枢…

2026/10/7 5:53:25 阅读更多 →
BqLog:面向游戏帧率的日志节律控制系统

BqLog:面向游戏帧率的日志节律控制系统

1. BqLog不是“日志打印器”,而是游戏线程的呼吸节律控制器很多人第一次看到“BqLog”这个名字,下意识会把它当成一个增强版console.log——无非是加了颜色、时间戳、标签过滤而已。但如果你真这么想,就完全误判了它在《王者荣耀》这种毫秒级…

2026/10/7 5:52:24 阅读更多 →

日新闻

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:01:58 阅读更多 →
用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →
芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 7:15:40 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 5:29:09 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 6:26:51 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 8:21:32 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 4:21:51 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 1:18:13 阅读更多 →