ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JSP图书管理系统实战:从环境搭建到并发借书避坑指南

JSP图书管理系统实战:从环境搭建到并发借书避坑指南 简介这份资源是面向高校计算机相关专业学生与Java Web初学者的一份完整毕业设计文档主题为基于JSP的图书管理系统设计与实现可用于课程设计、毕业设计参考或自学练手。压缩包内共1个docx文件约438KB内容涵盖摘要、目录、开发背景、开发环境与技术选型等章节系统梳理了B/S架构、JSP技术、MySQL数据库与MyEclipse平台的应用思路。文档围绕管理员、教师、读者三类权限展开涉及图书信息管理、借阅归还、分类检索、逾期提醒与预约等业务模块并说明了JSP内置对象处理请求、会话管理与数据库交互的实现方式。目前已有284人学习浏览适合需要参考系统架构设计、权限划分与功能模块规划并希望快速形成完整设计文档框架的读者。1. 从一份“能跑起来”的 JSP 图书管理系统说起很多人搜“基于jsp的图书管理系统设计与实现”其实心里想的不是论文而是我手上只有 JDK 和一个 Tomcat能不能在半天内把借书、还书、查书这条主线跑通答案是可以的。JSP 这套技术栈虽然老但它的优势恰恰在于“没有魔法”——请求进来、Servlet 处理、JDBC 落库、JSP 渲染每一层都看得见摸得着。对课程设计、毕业设计或者想理解 Web 请求生命周期的工程师来说它依然是一个成本极低的练手载体。这篇文章不讲空泛的“系统概述”而是按一个真实可复现的顺序往下走先把图书管理系统的数据模型和分层结构定死再一步步把环境搭起来、把借还书这条核心链路写通然后集中处理 JSP 项目里最容易翻车的中文乱码、连接泄漏、并发借书这几类问题最后给出一套能验证系统是否真的可用的检查方法。适合手里有 JDK 基础、想用 JSP 把图书管理系统真正落地的人也适合已经写过一半、卡在某个报错上的人对照排查。2. 图书管理系统的数据模型与分层怎么定2.1 三张核心表撑起借还书主线图书管理系统的表不用多但字段必须一次定对否则后面改表会连带改一堆 JSP 和 Servlet。我一般只保留三张核心表图书表、读者表、借阅记录表。图书表存书的基本信息和库存读者表存借书人借阅记录表是连接两者的桥梁也是判断“这本书现在在谁手上”的唯一依据。-- 图书表库存用 total 和 available 两个字段分开记 CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL UNIQUE, title VARCHAR(100) NOT NULL, author VARCHAR(50), total INT NOT NULL DEFAULT 0, -- 馆藏总数 available INT NOT NULL DEFAULT 0 -- 当前可借数 ); -- 读者表用 card_no 做业务主键避免暴露自增 id CREATE TABLE reader ( id INT PRIMARY KEY AUTO_INCREMENT, card_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, phone VARCHAR(20) ); -- 借阅记录表归还时间 NULL 表示还没还 CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, book_id INT NOT NULL, reader_id INT NOT NULL, borrow_time DATETIME NOT NULL, return_time DATETIME DEFAULT NULL, status TINYINT NOT NULL DEFAULT 0 -- 0 借出 1 已还 );这里的关键设计是available字段。很多初版系统只记total借书时去borrow_record里数未归还记录逻辑上没错但每次借书都要做一次聚合查询并发一上来就容易出问题。把可借数单独存一列借书时直接对它做原子更新判断和扣减一步完成这是后面处理并发借书的基础。status字段和return_time看似冗余但查询“当前在借”时用status 0比用return_time IS NULL更直观也方便加索引。2.2 分层JSP 只负责显示Servlet 负责调度JSP 项目最容易写乱的地方是把数据库查询直接写进 JSP 页面。一旦这么写页面里既有 HTML 又有 JDBC改一个字段要翻好几个文件。我的做法是固定三层JSP 只做展示Servlet 接收请求并调用 ServiceDAO 负责所有 SQL。JSP 里不出现任何DriverManager或PreparedStatement。// BookDao.java所有 SQL 集中在这里 public class BookDao { // 查询可借图书列表供 JSP 展示 public ListBook findAvailable() throws SQLException { String sql SELECT id, isbn, title, author, available FROM book WHERE available 0 ORDER BY id DESC; ListBook list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Book b new Book(); b.setId(rs.getInt(id)); b.setIsbn(rs.getString(isbn)); b.setTitle(rs.getString(title)); b.setAuthor(rs.getString(author)); b.setAvailable(rs.getInt(available)); list.add(b); } } return list; } }DBUtil.getConnection()统一从连接池取连接try-with-resources保证 ResultSet、Statement、Connection 一定关闭。这一步看起来是常识但我在实际项目里见过太多因为忘记关连接跑几十次请求后 Tomcat 就卡死的情况。DAO 返回的是普通 Java 对象列表Servlet 把它塞进request.setAttributeJSP 用 JSTL 遍历整条链路职责清晰后面加缓存或换数据库都只动一层。3. 用 JDK Tomcat MySQL 把环境跑通3.1 环境版本与目录结构JSP 项目对环境版本比较敏感尤其是 Servlet API 和 Tomcat 的对应关系。下面这套组合是我在本地反复验证过、能稳定跑起来的搭配不追求最新只追求少报错。组件建议版本说明JDK8 或 11JDK 8 兼容性最好JDK 11 需注意模块化Tomcat8.5 或 9Tomcat 10 的包名变成 jakarta老代码要改MySQL5.7 或 8.08.0 驱动类名和时区参数有变化JDBC 驱动mysql-connector-java 8.x放 WEB-INF/lib 下IDEEclipse / IDEA用 Maven 管理依赖更省事目录结构按标准 Web 项目来src放 Java 代码WebContent或webapp放 JSP、CSS 和WEB-INF。WEB-INF/web.xml是部署描述符WEB-INF/lib放 JDBC 驱动 jar。JSP 页面放在WebContent根目录或子目录下但WEB-INF下的 JSP 不能被浏览器直接访问适合放需要登录才能看的页面。3.2 数据库连接与 web.xml 配置连接数据库有两种写法直接在代码里写DriverManager或者配 JNDI 数据源。课程设计里我推荐先用一个简单的DBUtil类把 URL、用户名、密码集中管理改起来方便。// DBUtil.java集中管理连接参数 public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/library?useUnicodetrue characterEncodingutf8serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PWD your_password; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(JDBC 驱动未找到, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } }URL 里的characterEncodingutf8和serverTimezone是两个必加参数。前者决定数据库读写中文是否正常后者在 MySQL 8.0 上不加会直接抛时区异常。Class.forName放在静态块里只执行一次避免每次取连接都反射加载驱动。如果项目要上生产把DriverManager换成连接池如 Druid 或 HikariCPgetConnection的调用方代码不用改这是分层带来的好处。web.xml里主要配 Servlet 映射和欢迎页。Servlet 3.0 之后可以用WebServlet注解代替 XML 配置但显式写web.xml对新手更直观能看清请求路径是怎么映射到类的。servlet servlet-nameBookListServlet/servlet-name servlet-classcom.library.servlet.BookListServlet/servlet-class /servlet servlet-mapping servlet-nameBookListServlet/servlet-name url-pattern/book/list/url-pattern /servlet-mapping welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-list映射路径/book/list和 JSP 里的表单 action 必须一致这是最常见的 404 来源。改完web.xml一定要重启 Tomcat热部署有时不会重新加载配置。4. 借书还书这条核心链路怎么写4.1 借书 Servlet判断、扣减、记录三步借书是整个系统里唯一需要保证数据一致的操作顺序不能乱。正确顺序是先查这本书可借数是否大于 0再扣减available最后插入借阅记录。三步必须在同一个事务里任何一步失败都回滚。// BorrowServlet.java处理借书请求 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int bookId Integer.parseInt(req.getParameter(bookId)); int readerId (int) req.getSession().getAttribute(readerId); Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 带条件的原子扣减available 0 才更新 String updateSql UPDATE book SET available available - 1 WHERE id ? AND available 0; try (PreparedStatement ps conn.prepareStatement(updateSql)) { ps.setInt(1, bookId); if (ps.executeUpdate() 0) { // 影响行数为 0 说明没库存 conn.rollback(); resp.sendRedirect(list?msgno_stock); return; } } // 2. 插入借阅记录 String insertSql INSERT INTO borrow_record (book_id, reader_id, borrow_time, status) VALUES (?, ?, NOW(), 0); try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setInt(1, bookId); ps.setInt(2, readerId); ps.executeUpdate(); } conn.commit(); // 两步都成功才提交 resp.sendRedirect(list?msgok); } catch (SQLException e) { if (conn ! null) try { conn.rollback(); } catch (SQLException ignore) {} throw new ServletException(e); } finally { if (conn ! null) try { conn.close(); } catch (SQLException ignore) {} } }这段代码的核心在UPDATE ... WHERE available 0。它把“判断有没有库存”和“扣减库存”合并成一条原子语句靠数据库的行锁保证并发安全。如果先SELECT再UPDATE两个请求同时读到available 1就会超借。executeUpdate()返回影响行数为 0 就说明库存已经被别人抢走直接回滚并提示。事务的setAutoCommit(false)和commit/rollback必须成对出现finally里关连接不能省。4.2 还书逻辑与 JSP 列表渲染还书比借书简单但同样要在一个事务里完成两件事把借阅记录标记为已还把图书可借数加回去。判断依据是borrow_record.id而不是book_id因为同一本书可能被多个人借过。// ReturnServlet.java还书核心片段 conn.setAutoCommit(false); // 1. 更新借阅记录只有 status0 的记录才能还 String sql1 UPDATE borrow_record SET status 1, return_time NOW() WHERE id ? AND status 0; // 2. 可借数加一 String sql2 UPDATE book SET available available 1 WHERE id (SELECT book_id FROM borrow_record WHERE id ?);WHERE status 0是防止重复还书的关键。如果用户连点两次还书按钮第二次影响行数为 0就不该再加库存。JSP 列表页用 JSTL 遍历 Servlet 传来的bookList每行放一个借书表单把bookId作为隐藏字段提交。% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table c:forEach varb items${bookList} tr td${b.title}/td td${b.author}/td td${b.available}/td td form actionborrow methodpost input typehidden namebookId value${b.id}/ input typesubmit value借阅 c:if test${b.available 0}disabled/c:if/ /form /td /tr /c:forEach /tablec:if在可借数为 0 时禁用按钮这是前端的第一道防线但后端 Servlet 里的available 0判断不能省因为按钮禁用只是体验优化绕过它太容易了。5. JSP 图书管理系统避坑与排查清单5.1 中文乱码三个位置都要设对现象图书标题在数据库里正常但页面上显示成问号或乱码。原因通常不是单一位置而是请求、响应、数据库连接三处编码不一致。解决要一次改全JSP 页面顶部% page contentTypetext/html;charsetUTF-8 %Servlet 里request.setCharacterEncoding(UTF-8)和response.setContentType(text/html;charsetUTF-8)JDBC URL 加characterEncodingutf8。三处缺一处都可能乱码尤其是 POST 表单提交的中文书名。5.2 连接泄漏Tomcat 跑一会儿就卡死现象系统刚启动正常操作几十次后页面一直转圈Tomcat 日志出现Too many connections。原因是 DAO 里某个分支提前 return没有走到close()。解决所有连接获取都用try-with-resources或者把关闭逻辑放进finally。排查时可以在DBUtil.getConnection()里打印当前连接数或者直接看 MySQL 的SHOW PROCESSLIST如果连接数只增不减基本就是泄漏。5.3 并发借书超借两个人同时借走最后一本现象库存只有 1 本两个用户同时点借阅结果两条借阅记录都插入成功available变成 -1。原因是先查后改中间有时间窗口。解决用第 4 章那条UPDATE ... WHERE available 0的原子语句靠影响行数判断是否成功。如果已经写成先查后改改成原子更新即可不需要引入额外的锁。5.4 404 与 500路径和空指针各占一半现象点借阅按钮跳 404或者页面报 500。404 多半是web.xml里的url-pattern和表单action不一致或者 JSP 放在WEB-INF下却被直接访问。500 常见于session.getAttribute(readerId)返回 null 后直接强转 int。解决先在浏览器地址栏确认请求路径再在 Servlet 里对 session 属性做 null 判断未登录就重定向到登录页而不是让它抛异常。5.5 JSP 页面加载后自动刷新一次现象有人搜“jsp页面让加载完后刷新一次”想在图书列表页做自动刷新。如果确实需要可以在 JSP 里加meta http-equivrefresh content30但要注意这会重置表单输入。更稳妥的做法是用 JavaScript 的setTimeout配合location.reload()并且只在没有未提交表单时执行。图书管理系统里我一般不建议列表页自动刷新因为用户可能正在填借书信息刷新会丢数据。6. 验证系统真的可用一套可复现的检查方法写完不代表能用我习惯用一套固定动作验证图书管理系统是否真的过关。第一步清空borrow_record表把某本书available设为 1然后用两个浏览器或一个正常窗口一个隐私窗口同时登录不同读者同时点借阅。正确结果是只有一个人成功另一个人看到“无库存”数据库里available为 0借阅记录只有一条。这一步能同时验证事务和原子扣减。第二步连续借还同一本书 20 次观察 MySQL 的SHOW PROCESSLIST连接数应该稳定在一个小范围不持续增长。第三步在图书标题里输入中文、英文、数字和特殊符号混合的内容保存后刷新页面确认显示一致。第四步直接访问WEB-INF下的 JSP应该返回 404 而不是页面内容。# 验证并发借书用 ab 或两个 curl 同时打借书接口 curl -X POST http://localhost:8080/library/borrow \ -d bookId1 -b JSESSIONIDxxx curl -X POST http://localhost:8080/library/borrow \ -d bookId1 -b JSESSIONIDyyy wait # 然后查库确认 available 和 borrow_record 是否一致这套检查做完基本能覆盖图书管理系统 80% 的隐藏问题。我自己的习惯是每次改完 DAO 或事务相关代码都重跑一遍并发借书那一步因为它最容易在重构中被改回“先查后改”。JSP 这套技术栈没有框架帮你兜底好处是每个问题都能追到具体那一行 SQL 或那一个close()坏处是少写一步就会以最直接的方式暴露出来。把上面这些点固定成自己的检查清单比事后翻日志快得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表