☰
Servlet+JSP+MySQL图书管理系统:从部署到借阅归还实现全解析
2026/9/28 5:29:59 网站建设 项目流程

简介:面向JavaWeb初学者的图书管理系统项目,采用JSP + Servlet + JavaBean + MVC分层模式开发,数据库选用MySQL 5,覆盖图书信息维护、学生借阅、归还、逾期处理以及用户登录等核心业务,适用于课程设计、毕业设计和JavaWeb项目实训。压缩包大小约8.74MB,内含完整项目源码、运行文档、配套论文及答辩PPT,支持导入MyEclipse或Eclipse搭配JDK1.7、Tomcat8、MySQL5.6/5.7直接运行,文档对部署步骤、数据库初始化和功能模块划分做了清晰说明。已有1321人浏览学习,代码由作者亲自测试运行,页面交互与核心事务逻辑完整可靠。读者借助源码、运行文档和论文,可以快速理解MVC各层职责、Servlet请求转发与重定向、JDBC数据库访问以及图书借阅归还的业务流程设计,同时也能参考答辩PPT梳理项目亮点,在此基础上扩展图书预约、管理员权限控制、借阅统计等进阶功能。

1. 拿到的这个压缩包,到底能帮你省多少事

如果你正在做 Java 课程设计或者毕业设计,大概率被“图书管理系统”甩过脸:老师给个题目,要求 Servlet + JSP + MySQL,还得有借阅归还的功能,最好再来一份还算像样的文档。网上搜出来的源码要么缺数据库脚本,要么是纯 JSP 早就跑不起来的老古董,要么解压之后发现文档是空的——光是把环境调通就够熬一个通宵。

这个以 “java图书管理系统 servlet+jsp+mysql学生借阅归还项目源码含文档报告.zip” 命名的项目,走的就是最主流也最容易复现的那条路:Servlet 当控制器、JSP 做页面、JDBC 连 MySQL,功能上覆盖图书管理、学生注册登录、借书还书这几条核心链路,外加一份课程设计报告和数据库脚本。它对口的场景非常明确:Java Web 课程设计、期末大作业、以及那些“只想把项目跑起来交差但不想从零手写”的人——注意,是“跑起来交差”,不是“靠它去面试”。如果你正处于这个阶段,这篇笔记把解压到跑通再到改造成自己的项目,全链路拆开讲,能少走至少两晚的弯路。

2. 先拆解项目的技术选型:为什么是 Servlet + JSP + MySQL,而不是别的

2.1 Servlet、JSP 和 MySQL 各自的角色

在动手部署之前,先把这个项目的分工搞清楚,不然改起代码来会到处碰壁。这个项目的请求链路是按照 Java Web 早期最经典的三层模型搭的:浏览器发起请求,打到 Servlet 上;Servlet 负责接收参数、调用逻辑、跳转页面;JSP 负责把结果渲染成 HTML 返回给浏览器;MySQL 只存数据,既不管页面也不管业务。

为什么是这个组合?原因很实在:Servlet 是这门课的考纲重点,JSP 在页面渲染上写起来比 Servlet 拼 HTML 要舒服太多,MySQL 又是零成本安装就能上手的数据库。这三者搭起来,结构一目了然——你的老师会一眼看到“懂 Java Web 基础规范”。相比之下,Spring Boot + MyBatis+Vue 的方案虽然更贴近工业界,但很多课程设计的评分标准里根本没有这一项,答辩的时候老师也不好切入提问。所以,选这个技术栈不是因为它新,而是因为在这个标题对应的问题域里,它就是性价比最高的答案。

2.2 项目在开发工具里的目录划分:不要试图一把梭把所有文件放在同一个包下

假设解压之后在src下看到的主要 Java 文件包括LoginServlet.java、BookServlet.java、BorrowServlet.java、DBUtil.java这些类,常见的划分方式是把它们按职责扔进controller或servlet包里,把数据库相关代码归入dao,把实体类丢到entity目录。这里的核心原则是:把你改动的范围控制住。比如要改“还书”的日期判断,你找BorrowServlet或者ReturnServlet,而不是去一堆乱放的文件里盲猜。

有个细节值得提:有些老项目的源码里会出现sevlet这个拼错的包名——标题里也写了sevlet,说明包名或者文件名里可能保留了同样的笔误。这不会影响编译,如果后续你引包的时候发现import报错,直接右键类名 Rename 调整即可,不要因此觉得项目是坏的。

配置层面,WEB-INF/web.xml是 Servlet 映射的老管家。打开看会发现每个 Servlet 的servlet-mapping都写在里面,比如:

<servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>controller.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping>

如果你的项目用@WebServlet("/login")注解(Servlet 3.0+ 的写法),就不需要改web.xml里的映射,新旧两种方式别重复配置同一路径,否则部署到 Tomcat 时启动会报冲突。

JSP 页面一般集中在WebContent或webapp目录下,login.jsp、book_list.jsp、borrow_record.jsp这些页面文件与 Java 类分离,改样式的时候不用重新编译 Java,这是一条能省时间的边界。

2.3 环境匹配:JDK 8 别挂 Servlet 6,Tomcat 9 才是标准答案

这里稍微讲一下版本之间的“适配契约”。Servlet 是个接口规范,Tomcat 是它的实现。Servlet 4.0 对应的正好就是 Tomcat 9 和 JDK 8,这是课程设计开源项目最常见的组合。JDK 11 可以用,但如果你碰到class file has wrong version这种启动报错,多半是编译器级别设高了。更常见的坑是:项目里用到javax.servlet依赖,你引入的 Tomcat 是 10.1.x,那包名已经变成jakarta.servlet了,直接导致所有import javax.servlet.*编译全红。

我的做法是固定组合(JDK 8 + Tomcat 9 + 宽松的编译级别),这些主流开源课程项目全部能在这个组合下跑起来。

3. 从零把项目跑起来:数据库脚本与 JDBC 连接是第一个鬼门关

3.1 建库建表脚本先导入,注意 MySQL 版本的两个差异点

绝大多数这类项目里都会带一个book_manager.sql或者library.sql,打开先看,不急着执行。你需要在 MySQL 的命令行或 Navicat 里新建一个数据库,专门给这个项目用:

CREATE DATABASE IF NOT EXISTS db_library DEFAULT CHARSET utf8mb4; USE db_library; SOURCE /你的路径/book_manager.sql;

如果你比较习惯命令行,上面这段一步到位;如果用 Navicat,选中数据库db_library之后右键运行 SQL 文件即可——注意导入前后一定要确认当前选中的数据库是你要导入的那个,不然表会落进别的库里。

表结构这一块,图书管理系统大致都会建这几张表:t_book(书名、作者、出版社、库存总量)、t_student(或t_user,学号/工号、姓名、密码)、t_borrow(借书ID、图书ID、学生ID、借书日期、还书日期、是否归还)。核心的借还逻辑靠t_borrow和t_book两张表关联完成——库存的变化以t_book中的数字为准,每借出一本就把库存减一,归还时加一。这时候要注意 MySQL 8.0 的问题:把表导进去之后如果 SQL 里有gmt_create这种字段并且报错,多半是旧版本日期类型的定义写法不兼容,默认值改成DEFAULT CURRENT_TIMESTAMP就可以解决。

3.2 数据库连接类别改错——尤其是 utf8 和时区引号的坑

项目里那个负责与 MySQL 打交道的类,通常叫DBUtil.java,它决定了你是连得上数据库还是被Access denied按在地上摩擦。常见写法是:

package util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/db_library?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASSWORD = "你的MySQL密码"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }

这里面有几个参数是血泪经验换来的,必须单独解释一下:

useSSL=false是关闭 SSL 握手,因为 MySQL 8.0 驱动默认开启 SSL,而本地的课程设计项目甚至 MySQL 服务端都没配证书,报SSL connection error是常态,跟上面那行热词错误完全对得上。serverTimezone=Asia/Shanghai是解决“服务器时区值一时区无法识别”的经典解法,“CST”在中国服务器上被 MySQL 解释成美国中部时间,结果就是日期永远差 13 个小时,加了时区参数后借还书的日期计算才不出幺蛾子。characterEncoding=utf8是保证中文不乱码的关键,列写简体中文书名和姓名能不能正常入库,就靠它。

如果你的 MySQL 是 5.7 或更老,驱动名要换成com.mysql.jdbc.Driver;8.0 以后只能用com.mysql.cj.jdbc.Driver。写错驱动类名会直接抛ClassNotFoundException,而且这个错不是在 IDE 里提示,而是在 Tomcat 启动过程中炸给你看,措辞是 “The origin server did not find a current representation for the target resource”。

USER和PASSWORD是这台机器上的 MySQL 账号密码,拿不到就去mysql -uroot -p试一下你想用的密码,试出来的那份写进去就好。

3.3 编译级别与库引用:为什么老项目在别人的电脑上编译一大堆红色波浪线?

打开项目后,IDE 里动不动就是一片红,最常见的原因不是代码有问题,而是三个环境小项没有对齐:JDK 版本、语言级别、项目里引用的 Jar 包。

这类项目一般会在根目录下带一个lib文件夹,里面放着mysql-connector-java-x.x.jar、jstl.jar这些必要的依赖。如果在 Eclipse/IDEA 里不把这个文件夹 Add as Library,代码里的import java.sql.*编译不过去查不出原因。具体动作以 IDEA 举个例子:File → Project Structure → Libraries → + → Java,选中lib目录,确定之后import的爆红至少能消掉一半。

用 Maven 管理依赖的项目会更好办一点,直接看pom.xml里的 artifact 版本,把它粘贴进依赖搜索框同步即可。这里倒不用刻意追求新版本,mysql-connector-java用 8.0.x,javax.servlet-api跟着 Tomcat 9 用 4.0.1,版本保持这个组合稳定优先。

搞定这三步之后,Tomcat 能正常启动,首页能打开,你才真正拿到了“项目已跑通”的入场券。

4. 把借阅归还这条主链路跑顺:Servlet 请求处理与 JSP 页面联动

4.1 借书功能的最小实现

借书在流程上是一个“提交表单 → 接收参数 → 校验库存 → 改库存与记录”的事务,这类项目的核心魅力就在这里:Servlet 把串行逻辑穿起来,JSP 只负责把你填好的表单内容传给后端。先看借书表单长什么样,通常是borrow.jsp或者内嵌在book_list.jsp的一小块表格里:

<form action="BorrowServlet" method="post"> <input type="hidden" name="bookId" value="${book.id}"> 学生学号:<input type="text" name="studentNo"> <button type="submit">借阅</button> </form>

bookId用 hidden 隐藏域传递,是因为用户列表里看到的是书名和作者,后端真正要操作的是数据库里的主键 ID,而不是书名这个可重复的字段。学生学号用文本框让用户输入,对应的是t_student表里的唯一标识。你在部署之后试着借一本库存为 0 的书,会跳到“借阅失败”的页面,这就是后端校验的结果。

4.2 Servlet 接收参数与业务处理

BorrowServlet.java的职责是把 JSP 提交过来的数据接住,然后决定“借阅成功”还是“借阅失败”。代码主体逻辑比较常见的是长这个样子的:

@WebServlet("/BorrowServlet") public class BorrowServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); int bookId = Integer.parseInt(req.getParameter("bookId")); String studentNo = req.getParameter("studentNo"); BorrowService service = new BorrowService(); boolean ok = service.borrowBook(bookId, studentNo); if (ok) { resp.sendRedirect("BorrowSuccessServlet"); } else { req.setAttribute("msg", "借阅失败:库存不足或学号不存在"); req.getRequestDispatcher("borrow_fail.jsp").forward(req, resp); } } }

req.setCharacterEncoding("UTF-8")放在doPost第一行,是处理中文乱码的开场动作;它管的是 POST 请求的 body 编码。如果你试图用 GET 方式提交中文参数,那要改 Tomcat 的server.xml里的连接器配置加上URIEncoding="UTF-8",这是另一个独立话题。逻辑上先校验学生和书是否存在、库存是否大于 0,再执行两步 SQL 更新(插入借阅记录 + 图书库存减 1),这两步在BorrowService.borrowBook()方法里最好开事务。

4.3 事务边界为什么要卡在 Service 而不是 Servlet

上面代码里我把具体逻辑交给了BorrowService,而不是在doPost里直接写 SQL,这是整个项目结构里值得你抄过去的一个分层习惯。BorrowService内部逻辑大致这样:

public boolean borrowBook(int bookId, String studentNo) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); StudentDao studentDao = new StudentDao(); Student s = studentDao.findByNo(conn, studentNo); BookDao bookDao = new BookDao(); Book b = bookDao.findById(conn, bookId); if (s == null || b == null || b.getStock() <= 0) { return false; } bookDao.decreaseStock(conn, bookId); borrowDao.insert(conn, bookId, s.getId(), new Date(), null); conn.commit(); return true; } catch (Exception e) { try { if (conn != null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } finally { try { if (conn != null) conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }

核心在于conn.setAutoCommit(false)之后,两步数据库操作要么全部提交,要么全部回滚。如果没开事务,就可能出现“库存减了 1 但借阅记录没插进去”或者反过来的状态,后面你统计借阅报表时对不上账,这个问题的排查成本远高于你开事务的这几行代码。常见的课程设计项目不会把Connection这个对象传进 DAO 方法,而是每个方法内部重新拿一条连接,那么事务边界就断了,这种设计在这个场景可以直接改掉。

4.4 还书流程与超期判断的处理

还书相对借阅更简单:定位到未归还的借阅记录,把return_date写成当天。但多数项目不止做这一步,它还会在还书时顺便判断是否超期,并记录一个“逾期天数”或“罚款金额”字段。这个判断放在 SQL 里比较常见,比如:

UPDATE t_borrow SET return_date = NOW(), is_returned = 1, overdue_days = DATEDIFF(NOW(), borrow_date) - 30 WHERE borrow_id = ? AND is_returned = 0;

别把DATEDIFF当成无限精确,它只按天截断,小时和分钟不会导致半天误差。如果想要更精准的时长,需要TIMESTAMPDIFF(HOUR, ...)配合取整。这一步跑通后,用户就能看到“应还日期”“已还日期”“是否逾期”这些列的正确跳变,项目的核心功能就算闭环了。

4.5 JSP 里的列表输出与 JSTL:少用 Java 脚本片断

看 JSP 页面时,最难受的写法是用<% %>包一段 Java 代码去拼循环列表。虽然项目能跑,但会牺牲页面的可读性——建议在能改的地方尽量用JSTL的c:forEach输出表格:

<table> <c:forEach items="${bookList}" var="book"> <tr> <td>${book.name}</td> <td>${book.author}</td> <td>${book.stock}</td> <td><a href="BorrowServlet?bookId=${book.id}">借阅</a></td> </tr> </c:forEach> </table>

注意,${book.name}实际上调的是Book类里getName()方法,JavaBean 规范在这里体现了价值。如果你改字段名,比如把bookName改成name,那么 JSP 里的表达式也得同步变化,改了 Java 不用同步 JSP 是导致页面显示空白的经典翻车场景。

Servlet 传数据给 JSP 的常见方式是:

req.setAttribute("bookList", service.findAllBooks()); req.getRequestDispatcher("book_list.jsp").forward(req, resp);

转发的核心特征是浏览器地址栏不变,还是/book_list.jsp;sendRedirect不一样,它是浏览器重新发起一次新请求,地址栏会变化。这两个术语是这类项目的核心考点,也是你改代码时要分清楚的关键节点。

5. 避坑清单:5 个让这类项目跑不起来的隐蔽原因

5.1 Tomcat 启动失败,报ClassNotFound但代码里明明有 Jar 包

现象:代码里import com.mysql.cj.jdbc.Driver不爆红,但 Tomcat 一启动或浏览器一访问就抛ClassNotFoundException: com.mysql.cj.jdbc.Driver。 原因:驱动依赖虽然在 IDE 的构建路径里,但没有打进运行环境。如果 Eclipse,确认 Jar 放在了WebContent/WEB-INF/lib目录下,而不是仅 Build Path;如果 IDEA,检查 Artifacts 的输出里是否包含 Jar。 解决:把mysql-connector-java-x.x.jar单独复制到WEB-INF/lib内,再 Clean 一次 Tomcat 的 work 目录,重启就好了。

5.2 登录页面点击“登录”后地址栏变成/login.jsp404

现象:按了登录按钮,URL 停在http://localhost:8080/library/login.jsp,并提示 404。 原因:常见的是web.xml或@WebServlet中映射的路径尖括号写错,或者表单的action指向了一个不存在的login.jsp路径。 解决:先用浏览器 F12 看 Network 面板,确认请求的地址是不是/login;如果确实请求到了 Servlet,但 404,重新检查@WebServlet("/login")里路径开头有没有少斜杠,表单里action是否使用了${pageContext.request.contextPath}/login以便添加上下文路径。

5.3 页面中文全部变成问号????

现象:JSP 页面上中文正常,数据库里也是正常中文,但页面输出变成一行问号。 原因:响应编码没设置。Tomcat 默认 ISO-8859-1 输出,中文字符超过其范围就被转成?。 解决:在 Servlet 的doGet/doPost里加一行resp.setContentType("text/html; charset=UTF-8"),同时在 JSP 页面第一行确认有<%@ page contentType="text/html; charset=UTF-8" %>。注意,JSP 页面本身还要保存为 UTF-8 编码,否则源代码里的中文就已经是乱码了。

5.4Access denied for user 'root'@'localhost'

现象:连数据库报无法访问,但用 Navicat 连同一个数据库却正常。 原因:DBUtil.java里写死了别的密码,或者项目配置里的账号压根不是这台机器上的 MySQL 账号。 解决:先用命令行mysql -uroot -p验证密码,然后把DBUtil.java或jdbc.properties文件里的值替换成验证过的真实账号密码。MySQL 8 的驱动对空密码和默认密码策略比较敏感,建议给 MySQL 设一个非空密码再试。

5.5 改了数据库连接配置但重启后没效果

现象:改完jdbc.properties里 URL 的库名,重启 Tomcat 看日志,连接报的还是老库。 原因:Tomcat 的内存缓存没有清理。修改了配置文件后,直接 Redeploy 不能保证清掉旧的 class 缓存里的静态配置。 解决:Better 的做法是在 IDEA 里先 Stop 再 Clean 项目,删除out目录(或者 Eclipse 里的target目录),然后再 Start。如果还不行,干脆到 Tomcat 安装目录的webapps下把旧目录删除再重新部署。

6. 验证这套系统合不合格的三个要点,以及我能给你的进阶方向

项目跑起来,并不意味着交付就结束。百分制里能不能拿到分甚至高分,通常要盯三个验证方向:功能链路是否完整、边界情况处理是否干净、文档里的“核心设计说明”能否对得上代码。

功能链路上,至少完整走一遍:注册/初始账号登录 → 查看图书列表 → 借书 → 库存变化 → 还书 → 记录可见 → 重复借同一本书时库存不足提示。

边界情况上,要做两个极端的测试:把库存改成 1,借两次看第二次是否成功;把当前日期改到应还日期之后,看还书是否会标记超期。这两条是答辩老师最爱挑的软肋。

文档报告方向上,这类项目附带的那份《课程设计报告》一般会有功能模块图、数据库 ER 图、核心代码说明。把这些与你改过的代码对齐,改过DBUtil就把截图换掉,改过表名就把 ER 图同步改,否则老师一边打开 PDF 一边看代码,对不上号反而减分。因为实际工作里,报告和代码一致本身就是评分点。

最后给一个可选的进阶方向,既不影响交付稳定性又比加分更有效果:把DBUtil改成基于HikariCP的数据库连接池,体验一下“每来一次借书请求不再重新连接 MySQL”的差异。这个改动只需引入一个HikariCP的 Jar,替换DBUtil里获取连接的方式,无论从实际系统的压测表现还是从谈话表现来说,都远远好过只拿着 DriverManager 访问数据库。我的一个习惯是:跑通之后抽一晚把 DAO 层的方法签名统一改成接口,再去答辩,因为当吸引老師关注的角度不只剩“为什么不用 Spring”。

希望这些经验能真的救到某个熬到凌晨跑不通项目的你,祝顺利跑通。

提示:项目跑通后先备份一份原压缩包,后续改任何代码都基于复制出来的另一份目录操作,这是所有 Java Web 老开发共同认可的后悔药。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询