简介这是一份基于 jspservletdaomysql 的图书馆图书借阅管理系统毕业设计源码适合计算机相关专业学生用于课程设计、毕业设计或项目初期演示。资源面向具备 Java Web 基础、希望学习传统 Servlet 分层架构的读者涵盖管理员登录、用户管理、图书管理、罚款缴纳以及学生登录、图书借阅、归还、借阅记录查看和密码修改等完整功能模块后台另置游客浏览图书与详情操作。压缩包共包含 247 个文件以 java 源码、jsp 页面、class 编译文件为主辅以 jpg/png 界面截图、js/css 前端资源、sql 数据库脚本及 jar 依赖包整体体积约 6.7MB结构清晰便于按模块阅读和二次开发。资源已提供管理员与普通学生测试账号内置运行环境说明代码经多轮测试验证下载后可直接导入 Eclipse、MyEclipse 或 IDEA 部署运行。目前已有 142 人学习浏览适合需要快速获取可运行毕设案例、理解图书借阅业务流程与后台权限控制的读者参考使用。1. 一个 jspservletmysql 图书馆图书借阅管理系统 zip 包值不值得打开期末倒数第二周你手里多了一个「jspservletmysql图书馆图书借阅管理系统源代码文档说明数据库.zip」。这类压缩包是 Java Web 课程设计里最常见的交付物JSP 负责把页面渲染出来Servlet 负责接住浏览器请求MySQL 负责把图书、读者、借阅记录落库。对于正在做课设、准备毕业设计答辩、或者想补 Java Web 基本功的人来说它比 Spring Boot 脚手架更有学习价值——因为它没有框架替你藏起 request、response、session 和 JDBC 连接每一步请求流转都能用断点看得清清楚楚。这篇就带你把这个 zip 从拆开、跑通到敢答辩的完整链路走一遍。2. 拆开 zip 看门道JSP 页面、Servlet 控制层和四张 MySQL 表怎么各司其职拿到压缩包第一件事不是双击运行而是先看结构。一个规范交付的课设包通常分成三块src目录放 Java 源码WebRoot或WebContent放 JSP 页面和静态资源sql目录放建库脚本另外还有一份文档说明。把这三块对应到 MVC 上你就能在答辩时讲清楚「谁在管界面、谁在管逻辑、谁在管数据」。2.1 为什么课程设计还在用 JSPServletMySQL而不是 Spring Boot先说个反直觉的结论这套技术栈在工业界是古董但在课程设计里依然是最稳妥的选择。原因不是它先进而是它足够「裸」。用 Spring Boot 写图书馆系统控制器里一个RequestMapping就把请求接走了学生很难说清楚 HTTP 请求到底经过了谁而 JSPServlet 里你要亲手在web.xml里配servlet-mapping要自己在doGet/doPost里拿参数、转页面Servlet 生命周期里的init()、service()、destroy()三个方法都是能在调试器里看到调用时机的。MySQL 这边同理。JDBC 的DriverManager.getConnection()、PreparedStatement、ResultSet这套手写链路虽然啰嗦但能让人真正理解「一条 SQL 是怎么从 Java 跑到数据库的」。等你之后回头再看 MyBatis、Hibernate会发现它们只是把这套手写过程自动化了底层还是那一套。所以无论是为了过查重还是为了真懂这个 zip 都值得打开。目录结构我一般会先看有没有web.xml。传统 JSP 项目打包成 war 部署到 Tomcat靠的就是这个文件里的 Servlet 映射和欢迎页配置。如果web.xml缺失大概率是个半成品导入 IDEA 后也要手动补。图书馆图书借阅管理系统/ ├── src/ │ ├── com/xxx/dao/ # 数据访问层写 JDBC 和 SQL │ ├── com/xxx/servlet/ # 控制层接收请求、调 DAO、跳转 JSP │ ├── com/xxx/entity/ # 实体类Book、Reader、Borrow 等 │ ├── com/xxx/util/ # DBUtil 连接工具、过滤器 │ └── jdbc.properties # 数据库连接配置 ├── WebRoot/ │ ├── login.jsp # 登录页 │ ├── index.jsp # 主框架页 │ ├── admin/ # 管理员功能页图书管理、读者管理 │ ├── css/ js/ # 静态资源 │ └── WEB-INF/web.xml # Servlet 映射、欢迎页、过滤器 ├── sql/ │ └── library.sql # 建库建表 初始数据 └── 文档说明.doc # 需求分析、数据库设计、测试这段结构的逻辑是浏览器请求先被 Tomcat 按web.xml里的映射交给对应 ServletServlet 调用 DAO 层的 JDBC 代码操作 MySQL拿到结果后request.setAttribute()再把数据转发给 JSP 渲染成 HTML。注意WEB-INF下的 JSP 不能直接通过 URL 访问必须经过 Servlet 转发这是课设里常用的权限控制手段——不给访问路径自然没人能绕过登录。2.2 四张表怎么设计图书、读者、借阅记录和管理员图书借阅系统的数据库设计是个经典的一对多模型拆开 sql 脚本后重点看四张表。管理员表admin独立于业务之外只负责登录图书表book和读者表reader是主表借阅记录表borrow是中间表同时引用图书和读者的主键。借还书时真正要面对的坑都藏在这张中间表的字段设计里。表名关键字段作用adminid, username, password管理员登录与鉴权bookid, book_name, author, publisher, stock, total图书库存stock 是剩余可借数量readerid, username, password, real_name, max_borrow读者信息max_borrow 限制最多借几本borrowid, book_id, reader_id, borrow_time, return_time, status借阅记录status 区分在借/已还/逾期stock和total分开设计是这套系统的一个关键点total是馆藏总量stock是当前可借量。还书时只加stock不动total这样图书总量不会被借阅行为改掉。借书时先SELECT stock FROM book WHERE id?判断大于 0 才执行UPDATE book SET stockstock-1这两个操作必须放同一个事务里否则并发下会超借。CREATE DATABASE IF NOT EXISTS library DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE library; CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, book_name VARCHAR(200) NOT NULL, author VARCHAR(100), publisher VARCHAR(200), total INT DEFAULT 0, stock INT DEFAULT 0 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE reader ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, real_name VARCHAR(50), max_borrow INT DEFAULT 5 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE borrow ( id INT PRIMARY KEY AUTO_INCREMENT, book_id INT NOT NULL, reader_id INT NOT NULL, borrow_time DATETIME, return_time DATETIME, status TINYINT DEFAULT 0, CONSTRAINT fk_borrow_book FOREIGN KEY (book_id) REFERENCES book(id), CONSTRAINT fk_borrow_reader FOREIGN KEY (reader_id) REFERENCES reader(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表 SQL 里的三个细节要注意整库和所有表都显式指定utf8mb4而不是utf8因为utf8mb4才能存下特殊字符和生僻字这是中文乱码的第一道防线外键约束FOREIGN KEY保证借阅记录不会指向不存在的图书或读者但代价是删除图书前必须先处理关联借阅记录borrow_time用DATETIME而不是TIMESTAMP因为图书借阅的时间范围可能超出TIMESTAMP的 2038 年上限课设数据量小DATETIME 更省心。2.3 文档说明里真正该看的三个部分zip 里那份文档说明大多数人只拿来凑查重但里面有三个部分是你答辩前必须吃透的。第一部分是数据流图或用例图它描述「管理员登录 → 图书管理 → 读者管理 → 借阅/归还 → 逾期统计」这条主链路画在答辩 PPT 里能直接当系统架构讲。第二部分是数据库设计说明通常包含 E-R 图和字段字典对照上一节的四张表把外键关系讲明白就行。第三部分是测试用例重点看有没有覆盖「借书时库存为 0」「读者已借满」「还书时间晚于应还时间」这三个边界场景——它们是你演示功能时的加分点也是最容易暴露系统缺陷的地方。3. 把 zip 变成跑得起来的本地服务JDK/Tomcat/MySQL 版本搭配与导入步骤代码看明白了接下来是让它在你自己电脑上跑起来。这一步翻车率最高但九成问题都集中在版本不匹配和环境变量上。我见过太多人卡在 Tomcat 起不来、MySQL 连不上最后把锅甩给源码——其实换个 JDK 或驱动就好了。3.1 环境版本怎么搭JDK 8、Tomcat 8.5/9、MySQL 5.7 或 8.0这套课设源码最稳的组合是JDK 8 Tomcat 8.5/9 MySQL 5.7 或 8.0。JDK 8 是兼容性最好的版本如果你的项目里没有用到高版本语法用什么 JDK 都行但 Tomcat 10 之后把包名从javax.servlet改成了jakarta.servlet老源码导入后直接报「程序包不存在」所以别装最新版 Tomcat 10。MySQL 8.0 的安装教程里要多做一步驱动要换成com.mysql.cj.jdbc.Driver连接串要加serverTimezoneAsia/Shanghai否则会出现时区报错。下面给一个已验证过的搭配表组件推荐版本说明JDK1.88u202 或更高课设源码基本按 Java 8 语法写的Tomcat8.5.x 或 9.0.x内含javax.servlet老项目开箱即用MySQL5.7.x 或 8.0.x5.7 用com.mysql.jdbc.Driver8.0 用com.mysql.cj.jdbc.DriverIDEA2022 社区版即可社区版支持 Tomcat 集成配置装完 JDK 后在命令行执行java -version确认版本装完 MySQL 后执行mysql -uroot -p能进交互终端就行。这两个验证动作各花十秒能省掉后面排查环境变量的一小时。Tomcat 不需要安装解压到纯英文路径即可路径里带中文会导致 war 部署时出现诡异的资源找不到问题。3.2 先把数据库搞定建库导数据比跑项目更优先项目能不能起来一半看数据库。导入 sql 文件有两种方式一种是 MySQL 命令行里用source另一种是 Navicat/DBeaver 里直接运行 sql 脚本。命令行方式更不容易出错推荐先用它。打开终端执行mysql -uroot -p Enter password: ******进入 MySQL 交互终端后依次执行CREATE DATABASE IF NOT EXISTS library DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE library; SET NAMES utf8mb4; SOURCE D:/course/library.sql;SET NAMES utf8mb4这行容易被忽略它的作用是告诉当前会话「客户端发来的 SQL 和返回的数据都按 utf8mb4 解释」。如果不执行而 sql 文件里又有中文初始数据比如书名「三体」很可能导入后变成乱码。SOURCE后面的路径要用正斜杠/或双反斜杠\\Windows 默认的D:\course\library.sql单反斜杠会被 Mysql 解释成转义符。导入完成后执行USE library; SHOW TABLES;能看到四张表就说明脚本执行成功了一半再执行SELECT * FROM book;确认中文数据没乱数据库这步才算真正过关。3.3 在 IDEA 里把传统 JSP 项目跑起来导入、配置、启动IDEA 打开这类项目的常见做法是File - Open选中项目根目录IDEA 会识别出这是一个 Java Web 项目但通常还需要手动指定几项配置。比较省事的方式是选择File - New - Project from Existing Sources然后一路 Next在导入类型里选 Web。项目导入后依次做三件事Project Structure - Modules里确认 Java SDK 是 1.8选Add Web把 Web 模块加上Web Resource Directory指向WebRoot或WebContent。Project Structure - Libraries里添加 Tomcat 的servlet-api.jar这个 jar 通常在 Tomcat 安装目录的lib下不加它源码里所有import javax.servlet.*都会标红。右上角Add Configuration - Tomcat Server - LocalDeployment选项卡里点选Artifact一般选带war exploded后缀的那个Application context 填/或者/library。启动 Tomcat 时会自动编译项目并部署控制台出现Server startup in ... ms才算成功。这里有个血泪经验如果你在 IDEA 里改了 JSP 页面却总是看到旧页面十有八九是没开Deployment里的Build Artifact on frame deactivationIDEA 没把新改动的 JSP 同步到 Tomcat 的部署目录。手动点菜单栏Build - Build Artifact强制重新编译即可这个按钮就是传统 JSP 项目修改后的后悔药。3.4 改数据库连接配置三个必须检查的参数项目导入后先别急着启动打开src下的jdbc.properties或DBUtil.java把数据库连接信息改成你自己的。这是整个部署过程里最容易改错的地方。jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/library?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password123456如果用的是 MySQL 5.7第二行jdbc.driver要改成com.mysql.jdbc.DriverURL 里的serverTimezone参数可以保留也可以去掉5.7 不强求。这里三个最容易翻车的参数characterEncodingutf8mb4必须带上它保证 Java 程序往 MySQL 写中文时按 utf8mb4 编码传输和数据库建库时的字符集保持一致serverTimezoneAsia/Shanghai是 MySQL 8.0 的必填项不加会报The server time zone value Öйú±ê׼ʱ¼ä原因是 MySQL 8.0 默认时区不是东八区useSSLfalse是关掉 SSL 加密握手本地开发环境开着 SSL 反而会拖慢连接速度。改完配置文件顺手检查DBUtil.java里有没有用Class.forName()加载驱动。常见写法是Class.forName(props.getProperty(jdbc.driver)); conn DriverManager.getConnection( props.getProperty(jdbc.url), props.getProperty(jdbc.username), props.getProperty(jdbc.password) );这段代码的逻辑是先通过反射把驱动类加载到 JVM再由DriverManager用 URL 里的协议jdbc:mysql找到对应驱动建立连接。如果启动时报ClassNotFoundException说明mysql-connector-java这个 jar 没进到项目的WEB-INF/lib目录——IDEA 的 Libraries 里加了依赖还不够war 打包时必须让 jar 出现在WebRoot/WEB-INF/lib下否则 Tomcat 运行时根本找不到它。4. 核心业务代码怎么读登录鉴权、图书 CRUD 和借还书的 SQL 边界项目跑起来后真正的学习才刚刚开始。源码怎么读我的经验是按用户操作路径读先登录再进主页再走一遍图书管理的增删改查最后看借书还书。这条路径走完你对 Servlet 生命周期、DAO 封装、事务边界这三个核心点的理解就超过绝大多数同组同学了。4.1 登录请求是怎么在 Servlet 生命周期里走完的打开LoginServlet.java这是整个系统请求链路的起点。用户在login.jsp输入用户名密码点提交表单methodpost把参数发给 Servlet。Tomcat 收到请求后先检查web.xml里有没有对应url-pattern的servlet-mapping找到后执行 Servlet 的service()方法service()按请求类型自动分发到doPost()。Servlet 生命周期里init()只在第一次请求时执行一次destroy()在容器关闭时执行这两个方法在课设里通常不管真正干活的是doPostWebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); AdminDao dao new AdminDao(); Admin admin dao.findByUsernameAndPassword(username, password); if (admin ! null) { HttpSession session request.getSession(); session.setAttribute(admin, admin); response.sendRedirect(request.getContextPath() /index.jsp); } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }注意代码里两个分流方式登录成功用sendRedirect失败用forward。这俩的区别是面试必考题sendRedirect是浏览器重新发起一次请求地址栏会变request里的数据全部丢失所以登录成功后要把用户信息放进sessionforward是服务器内部转发浏览器地址栏不变request里的属性还能接着用所以登录失败的提示消息用request.setAttribute传过去就行。开头那句request.setCharacterEncoding(UTF-8)决定 POST 请求体里的中文参数怎么解码漏了它用户名密码里的中文到 Servlet 里就是乱码。4.2 图书管理页DAO 查询与 JSP 循环渲染的完整链路登录进主页后图书列表页是理解「后端数据怎么变成前端表格」的最佳样本。流程是BookServlet的doGet里调BookDao.findAll()拿到ListBook塞进request.setAttribute(bookList, list)然后forward到book_list.jsp。JSP 里用 JSTL 标签库循环渲染public ListBook findAll() throws SQLException { String sql SELECT id, book_name, author, publisher, total, stock FROM book ORDER BY id; ListBook list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Book book new Book(); book.setId(rs.getInt(id)); book.setBookName(rs.getString(book_name)); book.setStock(rs.getInt(stock)); list.add(book); } } return list; }c:forEach items${bookList} varbook tr td${book.id}/td td${book.bookName}/td td${book.stock}/td td a hrefBookServlet?actioneditid${book.id}编辑/a a hrefBookServlet?actiondeleteid${book.id} onclickreturn confirm(确定删除吗)删除/a /td /tr /c:forEach这里有个值得说透的细节PreparedStatement用try-with-resources写在括号里连接、预编译语句、结果集三个对象在 try 块结束时自动关闭这是 JDBC 代码的规范写法避免连接泄漏。而 DAO 里的Connection是从DBUtil.getConnection()拿的实际项目里这一步要用数据库连接池比如 DBCP、C3P0、Druid课设里手写DriverManager也行但答辩时能说一句「生产环境会用连接池替代」是加分项。JSP 里的${book.id}是 EL 表达式它自动调用Book实体类的getId()方法。注意实体类的属性名必须和setXxx方法严格对应bookName对应setBookName写成booknameEL 表达式会静默返回空。这是 JSP 页面最常见的「黑匣子」问题页面该显示的地方是空白控制台也没报错查来查去是实体类 getter 写错了名字。4.3 借书还书为什么库存扣减必须放在一个事务里借书流程比图书 CRUD 多一层业务规则先查图书库存是否大于 0再查读者当前借阅数是否达到上限两个条件都满足才插入借阅记录并扣减库存。这三个操作任何一个失败其他操作都必须回滚否则会出现「借阅记录存在但库存没扣」或「库存扣了但没借阅记录」的数据不一致。看借书 DAO 的写法public boolean borrowBook(int bookId, int readerId) throws SQLException { String checkStock SELECT stock FROM book WHERE id? FOR UPDATE; String checkReader SELECT COUNT(*) FROM borrow WHERE reader_id? AND status0; String insertBorrow INSERT INTO borrow(book_id, reader_id, borrow_time, status) VALUES (?, ?, NOW(), 0); String deductStock UPDATE book SET stockstock-1 WHERE id?; Connection conn DBUtil.getConnection(); boolean success false; try { conn.setAutoCommit(false); // 关闭自动提交开启事务 // 1. 查库存FOR UPDATE 锁定这行防止并发超借 PreparedStatement ps1 conn.prepareStatement(checkStock); ps1.setInt(1, bookId); ResultSet rs ps1.executeQuery(); int stock 0; if (rs.next()) stock rs.getInt(stock); if (stock 0) return false; // 2. 查读者在借数量 PreparedStatement ps2 conn.prepareStatement(checkReader); ps2.setInt(1, readerId); ResultSet rs2 ps2.executeQuery(); if (rs2.next() rs2.getInt(1) 5) return false; // 3. 插入借阅记录 PreparedStatement ps3 conn.prepareStatement(insertBorrow); ps3.setInt(1, bookId); ps3.setInt(2, readerId); ps3.executeUpdate(); // 4. 扣减库存 PreparedStatement ps4 conn.prepareStatement(deductStock); ps4.setInt(1, bookId); ps4.executeUpdate(); conn.commit(); // 全部成功提交事务 success true; } catch (SQLException e) { conn.rollback(); // 任何一步异常整体回滚 throw e; } finally { conn.setAutoCommit(true); conn.close(); } return success; }这段代码里最关键的是conn.setAutoCommit(false)和SELECT ... FOR UPDATE。setAutoCommit(false)之后后面所有 SQL 都处于同一个事务里直到commit()或rollback()FOR UPDATE是行级锁它把book表里 id 对应的那行锁住另一个请求同时执行这句 SQL 时会等待直到前面的事务提交或回滚。课设阶段并发量小不加FOR UPDATE也能过但答辩老师问「两个管理员同时借最后一本书怎么办」时你能答出这两个机制档次立刻不一样。还书逻辑是反方向更新borrow表的return_time和status再执行UPDATE book SET stockstock1。这里的坑是status字段的语义——0 表示在借1 表示已还如果用DELETE删掉借阅记录那么这本书的历史借阅统计比如逾期分析就全丢了所以课设里几乎都用逻辑删除只改状态不删行。5. 部署运行避坑5 个让课设翻车的高频问题与排查顺序源码能跑起来是一回事换个环境就崩是另一回事。下面这五条是我见过最多的故障每条都按「现象 → 原因 → 解决」的顺序写你遇到问题时按这个顺序排查比自己瞎试快得多。这些坑不解决轻则 JSP 页面白屏重则项目根本部署不上去。5.1 启动 Tomcat 报 ClassNotFoundException: com.mysql.jdbc.Driver现象项目启动成功但一访问需要查数据库的页面就报ClassNotFoundException: com.mysql.jdbc.Driver控制台完整异常堆栈里还能看到DBUtil.java的行号。原因驱动 jar 没放到 Tomcat 的类加载路径里。IDEA 里你在Project Structure - Libraries加了依赖IDEA 编译时能找到但运行时是 Tomcat 在加载类它只看WEB-INF/lib目录不看 IDEA 的 Libraries。解决把mysql-connector-java的 jar 文件复制到WebRoot/WEB-INF/lib/下然后重新 Build Artifact。如果你用 Maven 管理依赖检查 pom.xml 里依赖 scope 是不是provided是的话改成compile因为provided表示「容器提供」Tomcat 里没有这个 jar 的话就会找不到驱动。5.2 数据库和页面双端中文乱码现象登录页输入中文用户名存到 MySQL 里变成???或者页面显示汉å—这类火星文。原因三层地方字符集不一致。第一层是 JSP 页面本身没声明pageEncodingUTF-8第二层是 Servlet 接收请求参数前没执行request.setCharacterEncoding(UTF-8)第三层是 MySQL 连接串少了characterEncodingutf8mb4。三层里任何一层漏了中文就接力棒式地乱掉。解决按顺序检查JSP 页面顶部加% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%每个doPost方法最开头加request.setCharacterEncoding(UTF-8);jdbc.properties里 URL 加上characterEncodingutf8mb4。改完重启 Tomcat清浏览器缓存再看。注意 POST 和 GET 的乱码原因不同GET 乱码是 Tomcat 默认 URI 编码不是 UTF-8要在server.xml的Connector上加URIEncodingUTF-8这一步课设里经常有人漏。5.3 部署后访问首页 404但控制台显示 Server startup现象Tomcat 启动日志显示Server startup in 5000 ms浏览器访问http://localhost:8080/却 404或者访问时提示The origin server did not find a current representation。原因IDEA 的 Deployment 配置里 Application context 和实际访问路径不匹配。如果你配置的 context 是/library那么访问地址应该是http://localhost:8080/library/而不是根路径。另一个常见原因是web.xml里welcome-file-list配置了index.jsp但项目根目录下没有这个文件Tomcat 找不到默认欢迎页。解决先看 IDEA 里 Artifact 的名称再确定 context。稳妥做法是在Deployment里把Application context改成/这样直接访问http://localhost:8080/就能进。如果index.jsp不在根目录比如在WEB-INF/pages/下就必须配置一个IndexServlet做转发或者调整 welcome-file 指向相对路径。5.4 连接 MySQL 8 时报 Public Key Retrieval is not allowed现象项目一启动控制台抛出Public Key Retrieval is not allowed数据库明明是好的Navicat 能连上Java 连不上。原因MySQL 8.0 默认的认证插件是caching_sha2_password它要求客户端在连接时获取服务器的公钥做加密而 JDBC 驱动出于安全考虑默认禁止自动获取。这个问题只在 MySQL 8.0 上出现5.7 没有。解决在连接串上加两个参数jdbc.urljdbc:mysql://localhost:3306/library?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue是显式允许客户端向服务器请求公钥useSSLfalse关掉 SSL两个参数搭配才能让 JDBC 驱动和 MySQL 8.0 正常握手。如果你的数据库用户用的是mysql_native_password认证插件也可以避开这个问题但改插件属于改服务器侧配置不如 JDBC 参数来得干净。5.5 还书后库存变成负数借阅记录对不上现象功能演示时连续借同一本书三次第一次库存从 5 变 4第二次从 4 变 3第三次从 3 变 2看起来正常但还书时只还了一本库存从 2 变 5多了两本。原因还书 SQL 写成了UPDATE book SET stocktotal把剩余库存重置成了馆藏总量或者借书时不判断stock0读者把库存借成负数后还书又在负数上加。本质是 SQL 里的stockstock1和stocktotal语义完全不同前者是增量后者是赋绝对值。解决借用第 4 章的事务模板还书统一用增量写法并且借书前必须有stock 0的检查。如果历史脏数据已经产生写一条修复 SQLUPDATE book SET stocktotal - (SELECT COUNT(*) FROM borrow WHERE book_idbook.id AND status0)把库存重置为「总数减去在借数」。这条 SQL 你可以在答辩前预处理一遍避免演示到一半露出马脚。6. 从能跑到敢答辩自测清单和三个值得动手的改造点项目能跑只是及格线敢在答辩现场演示才算真正完工。给你一份我在验收任意课设时都会过的自测清单图书增删改查走一遍、借书还书走一遍、用不存在的账号登录看报错、连续借同一本书验证库存、把浏览器语言切到英文看页面是否乱码、换一台电脑重新按第 3 章步骤部署。任何一个环节出问题都说明你对这个系统的掌握还有盲区。三个改造点能明显提升答辩质量。第一个是把DBUtil的DriverManager换成 Druid 连接池代码量只有几行但能讲出「连接复用、避免频繁创建连接」的性能观点。第二个是给borrow表加一个应还日期字段due_timeJSP 页面上用当前时间和它比较逾期记录自动标红——这是所有演示里最抢眼的功能。第三个是把web.xml里的 Servlet 映射改成WebServlet注解减少配置文件篇幅顺便展示你是懂注解开发的。这三个点每个改动都不超过半小时但能让老师觉得你不是只会跑别人代码。最后说个习惯这份 zip 里的代码我会在跑通第一遍后立刻把整个项目压缩一份带日期的备份放在另一个目录。后续每次改动前也做一次快照因为传统 JSP 项目不像 Git 分支改坏了很难撤回有备份才有后悔药。我的经验是课设改到第三版的时候你会感谢当初随手存的这两个 backup 文件。希望这份拆解能帮你把这个 zip 吃透答辩顺利。本文还有配套的精品资源点击获取