JavaWeb图书借阅管理系统实战:从表结构到借还书核心流程

JavaWeb图书借阅管理系统实战:从表结构到借还书核心流程 简介这是一份基于JavaWeb的图书借阅管理系统项目源码采用JSPJavaBeanMySQLTomcat经典架构适合JavaWeb初学者、课程设计或毕业设计学生参考。系统分为读者和管理员两端读者可注册登录、查询借阅图书、查看借阅历史、归还图书及修改个人信息管理员可对图书进行增删改查管理读者信息并查询全部借阅记录覆盖了图书借阅管理的核心业务流程。资源包共38个文件以JSP页面、Java类、SQL脚本、CSS样式及XML配置文件为主整体大小1.76MB同时附有项目运行说明和系统介绍文档可帮助快速导入部署并理解项目结构。目前已有531人学习下载适合需要完整项目案例或二次开发基础的同学获取参考。 图书借阅管理系统JavaWeb里被做烂了的题目但凡课程设计、毕业设计十个里面八个都是它。但恰恰是这种老项目最能检验流程细节数据库怎么写才能不冗余借书时多人同时操作怎么保证不超借还书逾期罚款是跑批还是实时算我在帮人改这类系统的过程中发现大多数版本只做到了能运行离可交付还有很大距离。这篇文章就结合我重写一版图书借阅管理系统的完整思路从表结构、VS Code 开发环境、登录权限到借还书核心流程把关键代码和踩坑经验全部摊开讲适合正在做 JavaWeb 课设或想系统过一遍 Servlet JSP MySQL 的同学参考。1. 为什么图书借阅管理系统值得你亲手写一遍1.1 这套系统到底在练什么很多人觉得图书借阅管理就是简单的增删改查不值得认真做。我的看法相反它麻雀虽小五脏俱全。图书信息管理是基础 CRUD借书和还书是事务和并发问题登录和权限是 Filter 和 Session 的典型场景逾期罚款又是业务规则计算的代表。这些点串起来几乎覆盖了 JavaWeb 后端开发所有基础知识点。我见过不少同学把系统写成了图书数据的维护页面书表的增删改查做得很用心但借书功能就写成了把 book 表的总数减一。一旦并发或者归还时没处理好库存数据迟早对不上。真正完整的借阅闭环应该包括读者登录、图书检索、借书扣减库存、还书恢复库存、逾期计算罚款、管理员对图书和读者的维护。每个环节都有值得抠细节的地方。1.2 需求拆解不只有 CRUD 的借阅闭环先明确角色。这套系统至少要分清两类人普通读者和管理员。读者登录后能查书、借书、还书、看自己的借阅记录管理员负责录入图书、管理读者、处理借阅异常。很多人把管理员和读者混在一个表里用一个 userType 字段区分这没问题但你必须想清楚权限控制怎么做。业务闭环我建议这么拆读者查询图书看到可借数量。借书时判断该读者未还数量是否达到上限判断这本书当前是否有库存。扣减库存生成一条借阅记录并登记应还日期。还书时恢复库存标记记录为已还并根据实际归还日期计算是否逾期。管理员可以查看所有借阅记录对逾期读者进行罚款操作。这个流程看起来简单编程时最容易出问题的就是第3步和第4步。后面我会专门讲。2. 数据库设计把库存和借阅记录拆在两张表里少走弯路2.1 三张核心表与字段设计我把表设计成三张book图书表、reader读者表、borrow_record借阅记录表。这是最直观、也是绝大多数成熟系统采用的结构。下面给出字段清单和设计说明。CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) UNIQUE, name VARCHAR(100) NOT NULL, author VARCHAR(50), publisher VARCHAR(50), total_count INT DEFAULT 0, -- 总库存 available_count INT DEFAULT 0, -- 当前可借数量 create_time DATETIME ); CREATE TABLE reader ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) UNIQUE, password VARCHAR(64), real_name VARCHAR(30), phone VARCHAR(20), max_borrow INT DEFAULT 5, -- 最多可借本数 create_time DATETIME ); CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, book_id INT, reader_id INT, borrow_date DATETIME, due_date DATETIME, return_date DATETIME, -- 实际归还时间未还则为 NULL status TINYINT DEFAULT 0, -- 0: 借出中1: 已归还 fine DECIMAL(10,2) DEFAULT 0, -- 罚款金额 FOREIGN KEY (book_id) REFERENCES book(id), FOREIGN KEY (reader_id) REFERENCES reader(id) );实际项目里还会加索引比如borrow_record(reader_id, status)方便查某个读者的在借记录borrow_record(book_id, status)则方便查看某本书是否还有未还的旧记录。这几个索引按查询习惯建即可不用太多。2.2 为什么可用数量不能靠统计借阅记录得到这是新手最容易犯的错。有人觉得 book 表里不需要available_count每次要查询的时候直接total_count - 未还借阅记录数不就行了表面上可行实际维护成本很高。第一计算会慢。每查一本书就要子查询一张借阅记录表数据量大了以后性能很难看。第二一致性难保证。如果借书和还书的更新先改借阅记录再改 book 表两步之间一旦程序崩溃两边就会对不上。所以正确的做法是在 book 表保存一个available_count借书时减一、还书时加一并且用数据库事务保证这两步要么都成功要么都失败。我把它类比成超市库存货架上显示剩余数量而不是每次结账都去翻历史销售明细。2.3 逾期罚款用计算而非定时任务逾期罚款不建议做一个定时任务去天天扫描数据库。原因很简单罚款额度取决于实际归还时间。在没还书之前逾期天数每天都在变但罚款金额只需要在还书那一刻算一次就够了。在borrow_record表里存放fine字段还书时根据due_date和当天日期计算写入字段。这样历史记录可追溯哪怕后来改罚款规则也不会影响已还的记录。设计上只加一个return_date通过是否为 NULL 判断借出状态比单独维护一个状态位更直观。status字段建议保留因为后续如果要支持丢失预约等状态光靠时间字段表达不了。3. 用 VS Code 搭好 JavaWeb 开发环境我的实际步骤3.1 环境清单和插件很多人做 JavaWeb 默认用 Eclipse 或 IDEA但如果你只是想快速验证代码、而且平时习惯用 VS Code完全可以用 VS Code 跑通 Servlet JSP 项目。我实测过的组合是JDK 1.8 或 11我用的是 8Tomcat 9VS Code插件Extension Pack for Java必装、Tomcat for VS Code必装MySQL 8.0 Navicat 或命令行装完插件后VS Code 对 Java 项目的编译、运行、调试基本够用。唯一要注意的是VS Code 对 Maven 项目的支持好于传统 Web 项目所以建议直接用 Maven 结构创建别手动建一堆 lib 目录。3.2 工程目录与 Tomcat 配置我习惯用 Maven 的 war 包结构book-manager ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── com/example/book │ │ │ ├── filter │ │ │ ├── servlet │ │ │ ├── dao │ │ │ └── util │ │ ├── resources │ │ └── webapp │ │ ├── WEB-INF │ │ │ ├── web.xml │ │ │ └── jsp │ │ └── index.jsp在pom.xml里加上javax.servlet-api和mysql-connector-java依赖注意 servlet-api 要设置为providedscope因为 Tomcat 自带该 jar避免冲突。VS Code 启动项目有个小坑直接用Tomcat for VS Code插件时它要求有.vscode/tomcat.code-workspace之类的配置否则可能识别不到 war 包。切换标签页找到 Tomcat Server 视图把本地 Tomcat 路径添加进去即可。点击绿色启动按钮后插件会部署并把浏览器打开如果端口被占用改一下server.xml里的端口或者右键服务修改监听端口。3.3 第一个 Servlet 怎么跑通为了验证环境没问题先写一个最简单的TestServlet走一遍流程WebServlet(/hello) public class TestServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType(text/html;charsetUTF-8); resp.getWriter().write(h1JavaWeb 环境 OK/h1); } }只要浏览器访问http://localhost:8080/项目名/hello能看到这行字说明 Tomcat Maven VS Code 的三方配合没问题。这一步能过滤掉 80% 的环境问题再往下写业务逻辑才不慌。4. 核心流程实现登录过滤、借书并发、归还罚款4.1 用 Filter 把未登录用户挡在门外登录和权限校验不要在每个 Servlet 里重复写用 Filter 统一拦截就行。我在web.xml或者用WebFilter注解配置一个LoginFilterWebFilter(/*) 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(); // 放行登录页、静态资源和登录接口本身 if (uri.endsWith(/login.jsp) || uri.endsWith(/login) || uri.contains(/css/) || uri.contains(/js/)) { chain.doFilter(req, resp); return; } if (request.getSession().getAttribute(user) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }如果你区分管理员再写一个AdminFilter或者在一个 Filter 里判断用户登录后在 session 里存一个role字段管理员接口检查 role不满足就跳转 403 页面。简单的角色系统不需要引入 Spring Security这个方案够用且直观。4.2 借书数据库原子更新防止可借数量出现负数借书流程的关键在于并发。我举一个具体场景某本书只剩最后 1 本两个读者同时点借书。如果你的代码是先SELECT available_count判断大于 0再UPDATE book SET available_count available_count - 1两个请求可能同时读到 1然后都执行更新库存最终变成 -1。这就是典型的超卖问题。解决办法是用一条 SQL 原子更新Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); // 1. 原子扣减库存只有影响行数为 1 才说明抢到了这本书 String sql UPDATE book SET available_count available_count - 1 WHERE id ? AND available_count 0; PreparedStatement ps conn.prepareStatement(sql); ps.setInt(1, bookId); int rows ps.executeUpdate(); if (rows 0) { conn.rollback(); // 提示读者库存不足 return; } // 2. 检查该读者未还数量是否已达上限 // 3. 插入借阅记录 conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); DBUtil.close(conn); }available_count 0这个条件必须放在 WHERE 里配合行锁MySQL 会锁住这行记录直到事务结束这样并发时后续请求只能等前一个事务提交后再执行。这里用事务保证扣库存和插入借阅记录的一致性否则库存扣了但记录没插数据就乱了。判断读者是否超限的 SQL 也要注意必须在同一个事务内查borrow_record因为同一时间可能有多个借书请求避免两边都发现没超限最后超了。4.3 还书逾期天数和罚金的计算细节还书逻辑相对简单但计算日期时建议直接用LocalDate别再用Date加减。LocalDate dueDate LocalDate.parse(record.getDueDate()); LocalDate returnDate LocalDate.now(); long days ChronoUnit.DAYS.between(dueDate, returnDate); if (days 0) { double fine days * 0.1; if (fine 10) fine 10; // 单本书罚款封顶 10 元 }SQL 更新的时候别忘了同时更新book.available_count加一并把borrow_record里的return_date和fine、status一起更新。这同样要放在一个事务里。我见过有人写还书时忘了恢复库存结果没多久系统的可借书越来越少最后只能手动改数据库。另外还要处理一个边界情况图书被归还时如果这本书已经删除了怎么办建议外键设计时不要强制级联删除而是给 book 表加一个is_deleted标记或者借阅记录只保存book_id就算图书下架也不影响历史记录查询。5. 项目上线前这些加分项和坑值得注意5.1 事务与连接池别把 SQL 执行错当业务结束如果项目里到处都直接用DriverManager.getConnection()每访问一次数据库就新建连接性能会非常差。建议至少用Druid或HikariCP连接池。配置连接池时我建议在src/main/resources/druid.properties写driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/library?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai usernameroot password123456 initialSize5 maxActive20再用一个静态工厂获取数据源所有 DAO 都从这个工厂拿连接。这样能做到连接复用并发能力比新手默认写法强一个量级。5.2 对接在线支付的可行方案如果你想把罚款缴纳做成在线支付让系统显得更完整可以引入支付平台的沙箱环境来做模拟对接。核心思路是生成一个支付订单前端展示付款二维码用户支付后支付平台异步回调后端接口后端更新订单状态和罚款记录。不要把支付回调逻辑写在主流程里而是单独建一个PayNotifyServlet校验签名后更新数据库。因为回调可能重复推送所以要做幂等处理比如先查订单状态已处理则直接返回成功。这里要提醒一点真正的线上支付需要商户资质课程设计里用沙箱环境演示即可。别在公网项目里随便接入真实支付安全风险和合规风险都很高。5.3 部署时的常见错误清单我整理过几个高频部署报错几乎每次都能用上数据库中文乱码连接串必须加characterEncodingutf8mb4同时数据库、表、字段的字符集也要一致。Tomcat 启动端口冲突在conf/server.xml里改port三个端口都要改或者停掉占用端口的进程。找不到 JSP 或 Servlet检查项目是否打成 war 包context path 是否正确。MySQL 驱动加载失败确认pom.xml里依赖没设置scopeprovided/scope那是给 servlet-api 用的。另外部署到云服务器时记得开放 8080 端口数据库不要用 root 账号加弱密码至少建一个业务账号并限制 IP。6. 我个人重写这套系统时的几点体会如果让我再重写一遍我会把日志从一开始就加上而不是等出了问题再补。像借书、还书、罚款这些关键操作分别记录操作时间、操作人、操作结果排查数据不一致时能省非常多时间。代码层面尽量把 DAO 层、Service 层分开别把 SQL 写在 Servlet 里。刚写的时候觉得 Servlet 直接查库很爽改到后面你就会后悔因为一旦业务逻辑复杂起来Servlet 会被代码塞满连调试都困难。另一个建议是务必保留一份完整的测试数据。课程设计最怕答辩前数据被改乱了准备一个 SQL 初始脚本包含几十本不同类型图书和几个测试账号一键初始化到干净状态。这也是我后来养成的习惯每次演示之前跑一遍脚本比手工删数据可靠得多。图书借阅管理系统听起来普通但把每个细节都做到位你对 JavaWeb 的理解会上一个台阶。希望这篇分享能帮你少踩一些我已经踩过的坑。本文还有配套的精品资源点击获取