简介:这是一份基于 jsp+servlet+dao+mysql 的图书馆图书借阅管理系统毕业设计源码,适合计算机相关专业学生用于课程设计、毕业设计或项目初期演示。资源面向具备 Java Web 基础、希望学习传统 Servlet 分层架构的读者,涵盖管理员登录、用户管理、图书管理、罚款缴纳,以及学生登录、图书借阅、归还、借阅记录查看和密码修改等完整功能模块,后台另置游客浏览图书与详情操作。压缩包共包含 247 个文件,以 java 源码、jsp 页面、class 编译文件为主,辅以 jpg/png 界面截图、js/css 前端资源、sql 数据库脚本及 jar 依赖包,整体体积约 6.7MB,结构清晰,便于按模块阅读和二次开发。资源已提供管理员与普通学生测试账号,内置运行环境说明,代码经多轮测试验证,下载后可直接导入 Eclipse、MyEclipse 或 IDEA 部署运行。目前已有 142 人学习浏览,适合需要快速获取可运行毕设案例、理解图书借阅业务流程与后台权限控制的读者参考使用。
1. 一个 jsp+servlet+mysql 图书馆图书借阅管理系统 zip 包,值不值得打开
期末倒数第二周,你手里多了一个「jsp+servlet+mysql图书馆图书借阅管理系统+源代码+文档说明+数据库.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 为什么课程设计还在用 JSP+Servlet+MySQL,而不是 Spring Boot
先说个反直觉的结论:这套技术栈在工业界是古董,但在课程设计里依然是最稳妥的选择。原因不是它先进,而是它足够「裸」。用 Spring Boot 写图书馆系统,控制器里一个@RequestMapping就把请求接走了,学生很难说清楚 HTTP 请求到底经过了谁;而 JSP+Servlet 里,你要亲手在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里的映射交给对应 Servlet,Servlet 调用 DAO 层的 JDBC 代码操作 MySQL,拿到结果后request.setAttribute()再把数据转发给 JSP 渲染成 HTML。注意WEB-INF下的 JSP 不能直接通过 URL 访问,必须经过 Servlet 转发,这是课设里常用的权限控制手段——不给访问路径,自然没人能绕过登录。
2.2 四张表怎么设计:图书、读者、借阅记录和管理员
图书借阅系统的数据库设计是个经典的一对多模型,拆开 sql 脚本后重点看四张表。管理员表admin独立于业务之外,只负责登录;图书表book和读者表reader是主表;借阅记录表borrow是中间表,同时引用图书和读者的主键。借还书时真正要面对的坑,都藏在这张中间表的字段设计里。
| 表名 | 关键字段 | 作用 |
|---|---|---|
| admin | id, username, password | 管理员登录与鉴权 |
| book | id, book_name, author, publisher, stock, total | 图书库存,stock 是剩余可借数量 |
| reader | id, username, password, real_name, max_borrow | 读者信息,max_borrow 限制最多借几本 |
| borrow | id, 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 stock=stock-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 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 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 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 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 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 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) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;建表 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,连接串要加serverTimezone=Asia/Shanghai,否则会出现时区报错。下面给一个已验证过的搭配表:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8(8u202 或更高) | 课设源码基本按 Java 8 语法写的 |
| Tomcat | 8.5.x 或 9.0.x | 内含javax.servlet,老项目开箱即用 |
| MySQL | 5.7.x 或 8.0.x | 5.7 用com.mysql.jdbc.Driver,8.0 用com.mysql.cj.jdbc.Driver |
| IDEA | 2022+ 社区版即可 | 社区版支持 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 -> Local,Deployment选项卡里点+选Artifact,一般选带war exploded后缀的那个,Application context 填/或者/library。
启动 Tomcat 时会自动编译项目并部署,控制台出现Server startup in ... ms才算成功。这里有个血泪经验:如果你在 IDEA 里改了 JSP 页面却总是看到旧页面,十有八九是没开Deployment里的Build Artifact on frame deactivation,IDEA 没把新改动的 JSP 同步到 Tomcat 的部署目录。手动点菜单栏Build -> Build Artifact强制重新编译即可,这个按钮就是传统 JSP 项目修改后的后悔药。
3.4 改数据库连接配置:三个必须检查的参数
项目导入后先别急着启动,打开src下的jdbc.properties或DBUtil.java,把数据库连接信息改成你自己的。这是整个部署过程里最容易改错的地方。
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/library?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=123456如果用的是 MySQL 5.7,第二行jdbc.driver要改成com.mysql.jdbc.Driver,URL 里的serverTimezone参数可以保留也可以去掉,5.7 不强求。这里三个最容易翻车的参数:characterEncoding=utf8mb4必须带上,它保证 Java 程序往 MySQL 写中文时按 utf8mb4 编码传输,和数据库建库时的字符集保持一致;serverTimezone=Asia/Shanghai是 MySQL 8.0 的必填项,不加会报The server time zone value 'Öйú±ê׼ʱ¼ä',原因是 MySQL 8.0 默认时区不是东八区;useSSL=false是关掉 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输入用户名密码,点提交,表单method=post把参数发给 Servlet。Tomcat 收到请求后,先检查web.xml里有没有对应url-pattern的servlet-mapping,找到后执行 Servlet 的service()方法,service()按请求类型自动分发到doPost()。Servlet 生命周期里init()只在第一次请求时执行一次,destroy()在容器关闭时执行,这两个方法在课设里通常不管,真正干活的是doPost:
@WebServlet("/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里的数据全部丢失,所以登录成功后要把用户信息放进session;forward是服务器内部转发,浏览器地址栏不变,request里的属性还能接着用,所以登录失败的提示消息用request.setAttribute传过去就行。开头那句request.setCharacterEncoding("UTF-8")决定 POST 请求体里的中文参数怎么解码,漏了它,用户名密码里的中文到 Servlet 里就是乱码。
4.2 图书管理页:DAO 查询与 JSP 循环渲染的完整链路
登录进主页后,图书列表页是理解「后端数据怎么变成前端表格」的最佳样本。流程是:BookServlet的doGet里调BookDao.findAll()拿到List<Book>,塞进request.setAttribute("bookList", list),然后forward到book_list.jsp。JSP 里用 JSTL 标签库循环渲染:
public List<Book> findAll() throws SQLException { String sql = "SELECT id, book_name, author, publisher, total, stock FROM book ORDER BY id"; List<Book> 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}" var="book"> <tr> <td>${book.id}</td> <td>${book.bookName}</td> <td>${book.stock}</td> <td> <a href="BookServlet?action=edit&id=${book.id}">编辑</a> <a href="BookServlet?action=delete&id=${book.id}" onclick="return 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 status=0"; String insertBorrow = "INSERT INTO borrow(book_id, reader_id, borrow_time, status) VALUES (?, ?, NOW(), 0)"; String deductStock = "UPDATE book SET stock=stock-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 stock=stock+1。这里的坑是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 页面本身没声明pageEncoding="UTF-8";第二层是 Servlet 接收请求参数前没执行request.setCharacterEncoding("UTF-8");第三层是 MySQL 连接串少了characterEncoding=utf8mb4。三层里任何一层漏了,中文就接力棒式地乱掉。
解决:按顺序检查,JSP 页面顶部加<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>;每个doPost方法最开头加request.setCharacterEncoding("UTF-8");;jdbc.properties里 URL 加上characterEncoding=utf8mb4。改完重启 Tomcat,清浏览器缓存再看。注意 POST 和 GET 的乱码原因不同,GET 乱码是 Tomcat 默认 URI 编码不是 UTF-8,要在server.xml的<Connector>上加URIEncoding="UTF-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.url=jdbc:mysql://localhost:3306/library?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/ShanghaiallowPublicKeyRetrieval=true是显式允许客户端向服务器请求公钥,useSSL=false关掉 SSL,两个参数搭配才能让 JDBC 驱动和 MySQL 8.0 正常握手。如果你的数据库用户用的是mysql_native_password认证插件,也可以避开这个问题,但改插件属于改服务器侧配置,不如 JDBC 参数来得干净。
5.5 还书后库存变成负数,借阅记录对不上
现象:功能演示时连续借同一本书三次,第一次库存从 5 变 4,第二次从 4 变 3,第三次从 3 变 2,看起来正常;但还书时只还了一本,库存从 2 变 5,多了两本。
原因:还书 SQL 写成了UPDATE book SET stock=total,把剩余库存重置成了馆藏总量;或者借书时不判断stock>0,读者把库存借成负数后,还书又在负数上加。本质是 SQL 里的stock=stock+1和stock=total语义完全不同,前者是增量,后者是赋绝对值。
解决:借用第 4 章的事务模板,还书统一用增量写法,并且借书前必须有stock > 0的检查。如果历史脏数据已经产生,写一条修复 SQL:UPDATE book SET stock=total - (SELECT COUNT(*) FROM borrow WHERE book_id=book.id AND status=0),把库存重置为「总数减去在借数」。这条 SQL 你可以在答辩前预处理一遍,避免演示到一半露出马脚。
6. 从能跑到敢答辩:自测清单和三个值得动手的改造点
项目能跑只是及格线,敢在答辩现场演示才算真正完工。给你一份我在验收任意课设时都会过的自测清单:图书增删改查走一遍、借书还书走一遍、用不存在的账号登录看报错、连续借同一本书验证库存、把浏览器语言切到英文看页面是否乱码、换一台电脑重新按第 3 章步骤部署。任何一个环节出问题,都说明你对这个系统的掌握还有盲区。
三个改造点能明显提升答辩质量。第一个是把DBUtil的DriverManager换成 Druid 连接池,代码量只有几行,但能讲出「连接复用、避免频繁创建连接」的性能观点。第二个是给borrow表加一个应还日期字段due_time,JSP 页面上用当前时间和它比较,逾期记录自动标红——这是所有演示里最抢眼的功能。第三个是把web.xml里的 Servlet 映射改成@WebServlet注解,减少配置文件篇幅,顺便展示你是懂注解开发的。这三个点每个改动都不超过半小时,但能让老师觉得你不是只会跑别人代码。
最后说个习惯:这份 zip 里的代码,我会在跑通第一遍后立刻把整个项目压缩一份带日期的备份放在另一个目录。后续每次改动前也做一次快照,因为传统 JSP 项目不像 Git 分支,改坏了很难撤回,有备份才有后悔药。我的经验是课设改到第三版的时候,你会感谢当初随手存的这两个 backup 文件。希望这份拆解能帮你把这个 zip 吃透,答辩顺利。
本文还有配套的精品资源,点击获取