尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

可运行JavaWeb图书馆管理系统源码:MySQL增删改查与借阅事务

可运行JavaWeb图书馆管理系统源码:MySQL增删改查与借阅事务 简介面向高校课程设计、毕业设计与Java Web入门者的图书馆管理系统完整源码包基于Servlet、JSP、JavaBean与MVC分层实现后端使用MySQL管理图书、读者、借阅和管理员数据可支撑图书信息维护、借阅归还、登录鉴权与权限区分等典型业务的学习和二次开发。压缩包共209个文件、约1.91MB主体为51个JSP页面、32个Java源文件和32个class文件并配有MySQL数据文件、SQL建库脚本、jar依赖、properties配置、tld标签库以及gif、jpg、js、css等页面资源覆盖视图展示、请求控制、DAO访问到数据库部署的完整链路。已有359人学习。读者可结合图书、读者、借阅、管理员等DAO模块理解增删改查实现进一步掌握JSTL与EL、session状态保持、请求转发与重定向、异常处理及Tomcat部署系统按实体划分数据表借阅记录关联图书与读者也有助于理解关系型数据库设计与外键使用适合作为课程作业、毕设原型和Java Web综合练习的参考底稿。1. 图书馆管理系统源码能干什么一套能跑通的 JavaWeb 增删改查骨架接手一个馆藏后台或者课程设计最耗时间的往往不是写业务而是把登录、列表、增删改查这套底子搭起来。这套 JavaWeb 图书馆管理系统源码 MySQL 版本给的就是一副能直接跑起来的底子图书的增删改查、读者管理、借阅归还、分类维护、按书名作者模糊检索配上 JSP 页面和一整套 JDBC 数据访问代码导入数据库、改一下连接配置就能登录进后台点。它适合两类人一是刚学完 Servlet 和 JSP想找一份结构规整、能逐行读懂的完整项目对照练手二是手上有个小系统要交付需要一套分层清楚、好往上加功能的起手代码省掉搭框架的两三天。不适合的也很明确——它没有做高并发设计也没有微服务和缓存那一套拿它当互联网级借阅平台用会很难受。把它当成一副骨架改造成自己要的东西才是最顺的用法。2. 技术栈与目录结构从 web.xml 一路摸到 DAO 层拿到一套陌生的 JavaWeb 源码我的习惯是先不看业务先看两样东西依赖包里有什么目录怎么分层。这两样决定了后面改代码时手往哪儿伸。这套图书馆管理系统的组织方式很典型原生 Servlet 做控制器JSP 做视图JavaBean 做数据载体JDBC 直连 MySQL中间靠一层手写的 DBUtil 管连接。没有 Spring 那套注解和容器所有对象的创建、依赖的传递都是显式 new 出来的看着啰嗦但对刚入门的人反而是好事——调用链摆在明面上不存在“注解一加、神秘生效”的黑匣子。2.1 运行环境与技术选型盘点这套代码的技术组合偏传统好处是资料多、报错好搜。下表是我实际跑这类项目时用的环境不是项目硬性声明按这个配基本不会遇到版本层面的玄学问题层次技术我一般用的版本说明语言JavaJDK8 或 11源码用 8 的语法写11 也能编译Web 容器Tomcat8.5 / 9不要用 10包名从 javax 变了会全线报错数据库MySQL5.7 / 8.08.0 要注意驱动类和时区参数数据访问原生 JDBC—手写 SQL、手写结果集映射连接管理连接池Druid / C3P0随源码也有版本是每次 DriverManager 直连视图JSP JSTL—页面里混着 EL 表达式和 scriptlet前端Bootstrap jQuery—表格、弹窗、表单校验都在这一层一个容易忽略的点Tomcat 10 之后 Servlet API 的包名从javax.servlet换成了jakarta.servlet源码里的 import 一行都编译不过。这不是代码写错了是容器选错了换回 8.5 或 9 立刻就好。类似的还有 MySQL 8 的驱动类老代码写com.mysql.jdbc.Driver在新驱动里虽然还能用但会打警告正确写法是com.mysql.cj.jdbc.Driver。2.2 目录结构与各层职责源码解压后是一个标准的 Eclipse 动态 Web 工程结构有些版本是 Maven 的 src/main/java逻辑一样。核心目录大致如下library-system/ ├── src/ │ ├── com/library/bean/ # 实体类Book、Reader、BorrowRecord、Category │ ├── com/library/dao/ # 数据访问层每个实体一个 DAO只写 SQL │ ├── com/library/service/ # 业务层借还书这类要跨表的逻辑放这里 │ ├── com/library/servlet/ # 控制器收参数、调 service、转发 JSP │ ├── com/library/filter/ # 登录拦截、字符编码过滤 │ └── com/library/util/ # DBUtil、分页封装、日期工具 ├── WebContent/ # 或 web/、WebRoot/ │ ├── WEB-INF/ │ │ ├── web.xml # Servlet 与 Filter 的注册入口 │ │ └── lib/ # mysql-connector、jstl、连接池 jar │ ├── jsp/ # book_list.jsp、reader_list.jsp 等页面 │ ├── css/ js/ images/ # 静态资源 │ └── login.jsp └── sql/ └── library.sql # 建库建表 初始数据分层的边界在代码里是要守住的Servlet 只做三件事——取参数、调 Service、决定跳哪个页面不写 SQLDAO 只碰数据库不做业务判断方法签名里出现“够不够借”这种判断就说明写串了Service 是把多个 DAO 操作拼成一个完整动作的地方事务也开在这一层。血泪经验是很多下载来的源码在 Servlet 里直接new BookDao().update(...)一开始跑得通等你加预约、加罚款就会发现同一段 SQL 散在五六个 Servlet 里改一次漏三处。2.3 数据库表结构与初始化脚本整套系统的地基是四张表图书、读者、分类、借阅记录。借阅记录表是关键它决定了“谁在什么时候借了哪本书、还没还”这件事能不能查清楚。下面是核心两张表的建法字段名按大多数版本的命名习惯来-- 图书表total 是馆藏总数stock 是当前可借数两者分开才能记录借出状态 CREATE TABLE book ( book_id int NOT NULL AUTO_INCREMENT COMMENT 图书主键, isbn varchar(20) NOT NULL COMMENT 国际标准书号业务上唯一, title varchar(100) NOT NULL COMMENT 书名列表页模糊查询字段, author varchar(50) DEFAULT NULL COMMENT 作者, category_id int DEFAULT NULL COMMENT 所属分类关联 category 表, total int NOT NULL DEFAULT 0 COMMENT 馆藏总数, stock int NOT NULL DEFAULT 0 COMMENT 可借数量借出减一归还加一, publish varchar(80) DEFAULT NULL COMMENT 出版社, PRIMARY KEY (book_id), UNIQUE KEY uk_isbn (isbn) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书信息; -- 借阅记录表归还时只更新 return_date不删记录历史可追溯 CREATE TABLE borrow_record ( record_id bigint NOT NULL AUTO_INCREMENT, book_id int NOT NULL COMMENT 借的哪本书, reader_id int NOT NULL COMMENT 谁借的, borrow_date datetime NOT NULL COMMENT 借出时间, due_date datetime NOT NULL COMMENT 应还时间借出时按天数算好, return_date datetime DEFAULT NULL COMMENT 实际归还时间NULL 表示未还, PRIMARY KEY (record_id), KEY idx_book (book_id), KEY idx_reader (reader_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT借阅记录;这段建表语句里有三个决策值得说。total和stock分开存是为了在图书列表页直接展示“总共 5 本可借 2 本”而不用每次去 count 借阅记录uk_isbn加了唯一索引防止有人重复录入同一本书导入初始数据时如果 SQL 报 Duplicate entry说明脚本里本身就有重复行return_date允许为 NULL用它来区分在借和已还比额外加一个 status 字段更省事查询在借记录就是WHERE return_date IS NULL。字符集统一用 utf8mb4别用 utf8后者在 MySQL 里其实是三字节的残缺实现存书名里的特殊符号会报错。3. 核心业务链路借一本书从点击到入库经历了什么页面上的一个“借阅”按钮背后是过滤器放行、Servlet 取参、Service 开事务、DAO 执行两条 UPDATE 加一条 INSERT 的一串动作。这条链路跑通了整套代码你也就摸透了七八成剩下的增删改查都是同一个套路的复制。这一章按请求经过的顺序拆三段拦截、查询、写操作。3.1 登录校验与权限过滤器登录拦截用 Filter 实现注册在 web.xml 里拦所有路径但要把登录页和静态资源放出去否则会出现“访问登录页被拦去登录页”的死循环。这是新手最常踩的第一坑public class LoginFilter implements Filter { Override 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/) || uri.contains(/images/)) { chain.doFilter(req, resp); return; } // 其余请求必须有 session没有就踢回登录页 Object user request.getSession().getAttribute(loginUser); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); // 已登录交给后面的 Servlet } }getRequestURI()拿到的是带上下文路径的完整路径所以判断用endsWith比equals稳项目部署成/library还是/都不影响。getContextPath()返回项目根路径重定向时拼上它否则在非根路径部署时会跳到容器根目录去。session 里存的 key 必须和登录 Servlet 里setAttribute的 key 完全一致大小写都算这一点经常因为复制粘贴改漏而翻车。放行名单里如果漏了/js/页面能打开但样式全丢看着像 CSS 路径写错其实是静态资源被 302 了。3.2 图书列表的模糊查询与分页列表页是使用频率最高的页面也是最容易写出性能问题的地方。合格写法是 SQL 层分页不要一次SELECT *全查出来再在 Java 里 subList。DAO 里两个方法配合一个查当前页数据一个查总数// 分页查询keyword 为空时走全量非空时按书名或作者模糊匹配 public ListBook page(String keyword, int pageNo, int pageSize) { ListBook list new ArrayList(); String sql SELECT * FROM book WHERE title LIKE ? OR author LIKE ? ORDER BY book_id DESC LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { String like % (keyword null ? : keyword.trim()) %; ps.setString(1, like); ps.setString(2, like); ps.setInt(3, (pageNo - 1) * pageSize); // 偏移量从 0 开始第 1 页是 0 ps.setInt(4, pageSize); // 每页条数一般 10 或 15 try (ResultSet rs ps.executeQuery()) { while (rs.next()) { list.add(toBook(rs)); // 结果集转实体字段名要和 SQL 别名对得上 } } } catch (SQLException e) { throw new RuntimeException(图书分页查询失败, e); } return list; } // 总数查询给分页条算总页数用条件必须和上面完全一致 public int count(String keyword) { String sql SELECT COUNT(*) FROM book WHERE title LIKE ? OR author LIKE ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { String like % (keyword null ? : keyword.trim()) %; ps.setString(1, like); ps.setString(2, like); try (ResultSet rs ps.executeQuery()) { return rs.next() ? rs.getInt(1) : 0; } } catch (SQLException e) { throw new RuntimeException(图书总数查询失败, e); } }LIMIT ?, ?的两个参数第一个是偏移量公式(pageNo - 1) * pageSize第二个是每页条数顺序写反了会出现“第一页只有 1 条、第二页从第 10 条开始”的诡异现象。count方法和page方法的 WHERE 条件必须逐字一致否则会出现总数 30 条、翻到第 2 页却是空的情况。参数全部走PreparedStatement占位符既防住了单引号引起的 SQL 报错也顺手挡了注入如果看到源码里用字符串拼WHERE title LIKE % keyword %那一段建议直接重写。3.3 借阅与归还一个必须包在事务里的操作借一本书要动两张表borrow_record插一条记录book的stock减一。这两步必须同生共死否则会出现“记录有了但库存没减”或者“库存减了但没记录”的脏数据对账时能把人对到怀疑人生。事务开在 Service 层用 JDBC 的手动提交public boolean borrow(int bookId, int readerId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关掉自动提交从这里开始一个事务 // 第一步扣库存。stock 0 是并发下的兜底避免扣成负数 String updateSql UPDATE book SET stock stock - 1 WHERE book_id ? AND stock 0; try (PreparedStatement ps conn.prepareStatement(updateSql)) { ps.setInt(1, bookId); if (ps.executeUpdate() 0) { // 影响 0 行说明库存已空或书不存在 conn.rollback(); return false; } } // 第二步写借阅记录应还时间默认借出后 30 天 String insertSql INSERT INTO borrow_record(book_id, reader_id, borrow_date, due_date) VALUES (?, ?, NOW(), DATE_ADD(NOW(), INTERVAL 30 DAY)); try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setInt(1, bookId); ps.setInt(2, readerId); ps.executeUpdate(); } conn.commit(); // 两步都成功才提交 return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ignore) { } throw new RuntimeException(借阅失败, e); } finally { try { if (conn ! null) conn.setAutoCommit(true); } catch (SQLException ignore) { } DBUtil.close(conn); // 归还连接池不是真关闭 } }关键点有三个。setAutoCommit(false)之后所有的 SQL 都在同一个连接上所以 DAO 不能自己再去DBUtil.getConnection()拿新连接否则事务就断了——这是改造这类代码时最容易犯的错一定要把 Connection 当参数往下传或者用 ThreadLocal 绑定。update ... where stock 0这种写法叫乐观校验两个管理员同时点借阅只有一个人能成功另一个拿到影响 0 行直接提示“库存不足”比先查再判断稳得多。finally里把 autoCommit 还原再 close是因为连接池里的连接是复用的不还原会污染下一次使用。归还的逻辑正好反过来更新return_date加库存加一事务结构一模一样只是 SQL 换掉。4. 本地跑通从建库到登录首页的操作步骤代码读得再明白跑不起来也白搭。这一章按顺序给出一条最短路径每一步都标清楚失败时该看哪里。整套流程大概二十分钟前提是环境别选错版本。4.1 环境与依赖确认先确认三件事JDK 装好并且java -version能输出版本号Tomcat 解压后bin/startup.bat或.sh能起来浏览器访问localhost:8080能看到默认页MySQL 服务在跑能用一个账号登进去。这三样少一样后面所有报错都会指向错误的方向。依赖包放在WEB-INF/lib下一般包括mysql-connector-java-x.x.x.jar、jstl.jar、standard.jar以及连接池的 jarDruid 或 C3P0。如果解压后这个目录是空的说明源码没带依赖需要自己从本地仓库或可靠镜像补齐。少 jar 的典型表现是启动时抛ClassNotFoundException类名一看就知道缺哪个包。提示Eclipse 导入后如果项目上有红叉但代码没错先看 Build Path 里的 JRE 是不是指向了 Tomcat 自带的 JDK换成自己的 JDK 8 一般就好。4.2 建库导数据与改配置先建库再导表。命令行里执行两句就行# 登录 MySQL创建数据库并指定字符集 mysql -u root -p -e CREATE DATABASE library DEFAULT CHARSET utf8mb4; # 导入源码 sql 目录下的脚本注意路径换成自己解压后的实际位置 mysql -u root -p library ./sql/library.sql导完用SHOW TABLES;确认四张表都在。如果脚本里带了CREATE DATABASE和USE第一句可以省掉直接导就行如果报Unknown database说明脚本里没有建库语句得先手动建。接下来改配置文件。老一点的版本把连接参数写在src/jdbc.properties新一点的直接写死在 DBUtil 里两种都见过# 放在 src 根目录编译后会自动进 WEB-INF/classes jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://127.0.0.1:3306/library?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password换成你自己的密码逐项说一下com.mysql.cj.jdbc.Driver是 MySQL 8 驱动的类名用 5.x 驱动的话是com.mysql.jdbc.DrivercharacterEncodingutf8解决中文写入变问号serverTimezoneAsia/Shanghai必须加不然 MySQL 8 会在启动时报时区错误日志里那句The server time zone value ... is unrecognized就是它useSSLfalse避免本地连接时的一堆警告allowPublicKeyRetrievaltrue是 8.0 默认认证方式下必须加的不加会报公钥检索失败。如果源码里没有 properties 文件而是有个DBUtil类那就直接改里面的常量public class DBUtil { private static final String URL jdbc:mysql://127.0.0.1:3306/library ?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse; private static final String USER root; private static final String PWD 换成你自己的密码; // 每次调用取一个连接连接池实现会在这里做复用 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } // 空实现也要有方便以后换连接池时上层代码不用动 public static void close(Connection conn) { if (conn ! null) { try { conn.close(); } catch (SQLException ignore) { } } } }URL里的库名必须和上一步建的库一致改完记得重新编译WEB-INF/classes下的 class 不更新等于没改。如果源码用的是连接池配置项名字会变成url、username、password、initialSize这种语义一样只是前缀不同。4.3 部署运行与首次登录Eclipse 里右键项目 Run on Server或者手动把整个工程目录拷到 Tomcat 的webapps下然后启动容器。访问http://localhost:8080/项目名/login.jsp出现登录框就算成功了一半。web.xml 里 Filter 的注册长这样路径和类名要和代码对上filter filter-nameloginFilter/filter-name filter-classcom.library.filter.LoginFilter/filter-class /filter filter-mapping filter-nameloginFilter/filter-name !-- /* 拦截所有请求具体放行逻辑在 Java 代码里判断 -- url-pattern/*/url-pattern /filter-mappingurl-pattern写/*是对的因为白名单在 Filter 内部处理如果写成/jsp/*那直接访问根目录下的页面就绕过了校验。首次登录的账号密码一般在library.sql的reader或admin表的 INSERT 语句里明文或者 MD5 值都有明文直接输MD5 就得查一下对应的原文别硬猜。登进去之后第一件事是把图书列表、新增图书、借一本书、还一本书这四个动作各点一遍链路通了再谈改功能——这四步里任何一步报错都比后面加需求时才发现问题好修得多。5. 排查清单这套代码最容易翻车的五个地方下面五条是我在不同版本的 JavaWeb 图书管理源码上反复遇到的基本覆盖了八成启动期和运行期故障。按“现象 → 原因 → 解决”记遇到时对着看就行。5.1 页面中文全变问号或乱码现象登录进去后书名、作者显示成???或者一堆方块控制台打印的 SQL 参数也是乱码。原因三处编码没统一。JSP 页面头部没写pageEncodingUTF-8Servlet 取参前没调request.setCharacterEncoding(UTF-8)数据库连接的 URL 里没带characterEncodingutf8。三者缺一个就会在链路的某一环掉链子。解决JSP 顶部补% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %在过滤器里统一加一句request.setCharacterEncoding(UTF-8)要放在所有getParameter之前数据库连接串补上编码参数。改完重启容器别只刷新页面class 不重编不生效。5.2 启动就 404日志里没有明显异常现象Tomcat 起来了访问项目路径返回 404或者访问具体 Servlet 报“请求的资源不可用”。原因多半是访问路径和WebServlet或 web.xml 里的url-pattern对不上。比如代码里写的是/book/list你访问的是/bookList或者项目部署名不是你以为的那个实际路径是/library-system而你访问了/library。解决先看 Tomcat 启动日志里那行Deployment of web application archive ... has finished里面会打出真实上下文路径。再核对 Servlet 的映射WebServlet(/book/list)对应的请求就是项目名/book/list。路径里多一层少一层斜杠也会 404别嫌麻烦逐字对。5.3 数据库连接池报连接超时或连接已关闭现象点了几个页面之后开始报Communications link failure或者No operations allowed after connection closed重启容器又能撑一会儿。原因要么连接池的maxActive太小、连接借出去没还要么代码里在 DAO 里conn.close()之后又拿去用要么 MySQL 的wait_timeout比连接池的空闲检测短连接被服务端先掐了。解决确认每个getConnection都有对应的 close最好用 try-with-resources连接池配置里加上空闲检测testWhileIdletrue、validationQuerySELECT 1和minEvictableIdleTimeMillis实在拿不准就先把连接池换回 DriverManager 直连排除池的问题再逐步加回来。5.4 借完书库存没变或者能借成负数现象借阅记录插进去了但图书列表里可借数量还是原值或者连着点两次借阅库存变成 -1。原因扣库存的 UPDATE 和插记录的 INSERT 不在同一个事务里中间抛了异常但前面已经提交或者 UPDATE 语句没加stock 0条件并发下两个请求都判断通过。解决按 3.3 的写法把两条 SQL 放进同一个 Connection 的手动事务setAutoCommit(false)之后统一 commitUPDATE 的 WHERE 补上stock 0用影响行数判断成功与否另外检查图书表里stock和total的初始值有些脚本只填了totalstock默认 0那就一本都借不出去。5.5 日期字段插入报错或显示成 1970现象新增图书或借阅时抛Incorrect datetime value或者列表页显示的借出时间全是 1970-01-01。原因日期格式对不上。前端传的是2024-05-01这种字符串后端用new Date()直接塞进datetime字段或者 JDBC 用的是java.sql.Date但只保留了日期部分时间被截断再或者数据库时区和 JVM 时区不一致差 8 小时。解决统一用java.sql.Timestamp或LocalDateTime承载Servlet 里用SimpleDateFormat(yyyy-MM-dd)显式解析解析失败给默认值别抛空指针连接串里加serverTimezoneAsia/Shanghai页面展示时用 JSTL 的fmt:formatDate统一格式别在 JSP 里手写substring。6. 二次开发加一个“预约图书”功能要动哪几个文件拿到骨架之后多数人要做的第一件事就是加功能。拿“预约图书”举例这个需求比借书轻——不扣库存、不改借阅记录只记一条“某人想借某本暂时借完的书”。改动面不大但正好能练一遍从建表到页面的完整链路。先梳理要动的地方改动点文件说明建表sql/library.sql 追加reservation表记书、人、预约时间、状态实体bean/Reservation.java四个字段加 getter/setter照 Book 抄数据访问dao/ReservationDao.javainsert、按读者查、按状态查三个方法业务service/ReservationService.java判断是否已预约过、库存是否真的为 0控制器servlet/ReservationServlet.java收参数、调 service、返回 JSON 或转发页面jsp/book_list.jsp每行加一个“预约”按钮库存大于 0 时置灰注册web.xml 或注解新 Servlet 的映射控制器这一层保持薄只做参数收转WebServlet(/reservation/add) public class ReservationServlet extends HttpServlet { private final ReservationService service new ReservationService(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 读者身份从 session 取不要从请求参数取否则谁都能替别人预约 Integer readerId (Integer) request.getSession().getAttribute(readerId); String bookIdStr request.getParameter(bookId); response.setContentType(application/json;charsetUTF-8); if (readerId null || bookIdStr null) { response.getWriter().write({\code\:401,\msg\:\请先登录\}); return; } try { int bookId Integer.parseInt(bookIdStr); // 非数字会抛异常统一兜住 boolean ok service.reserve(bookId, readerId); response.getWriter().write(ok ? {\code\:0,\msg\:\预约成功\} : {\code\:1,\msg\:\库存充足或已预约过\}); } catch (NumberFormatException e) { response.getWriter().write({\code\:400,\msg\:\参数不合法\}); } } }业务层的判断顺序建议是先查该读者对该书是否有未完成的预约有就直接返回别重复插再查库存是否真的大于 0大于 0 说明能直接借不该走预约两条都过了才 insert。这个顺序反了会出现“书明明有货还被预约”的怪状态。验证方法很土但有效把某本书的stock手动改成 0然后在页面上点预约看数据库里有没有多一条状态为待处理的记录再把stock改回 1点预约应该被拒最后用同一个人点两次第二次应该提示已预约。三个用例全过这条链路就算接进去了。页面上的按钮建议按库存做禁用前端拦一道只是体验后端那两道判断才是真正的保险。从那以后我每接一套陌生的 JavaWeb 源码都强制自己先把登录链路和一条写操作完整走通再动任何一行业务代码——跳过这步直接改功能后面花在排查环境上的时间会成倍还回来。这套图书馆管理系统的价值也在这里它的分层足够朴素朴素到你可以在上面放心地加任何东西。希望帮到你。本文还有配套的精品资源点击获取
返回列表