
简介这是一份基于Java与MySQL实现的图书管理系统完整源代码包主要面向计算机专业正在做课程设计、毕业设计或需要项目实战练习的学生。项目已通过导师指导与审核评审分为98分源码经过本地编译和严格调试确保可以正常运行整体难度适中适合用来理解业务系统从数据库设计到界面交互的完整链路。压缩包内共50个文件包含40个Java源代码文件、1个SQL数据库初始化脚本、1个JDBC驱动JAR包以及E-R图、需求说明文档md等辅助材料资源总大小仅2.27MB目录按实体类、视图、控制器和工具模块清晰分包便于定位功能点与二次开发。下载后可以快速部署图书管理系统的核心功能包括图书信息管理、借还书流程、读者管理、数据库初始化等同时也能对照MVC分层思想、数据库表关系设计和Swing交互界面的具体实现来巩固Java项目实战能力。目前已有89人学习下载对毕业设计选题或课程项目实训具有较高参考价值。1. 图书管理系统源代码是什么一个 Java 入行绕不开的完整闭环又到交课设的节点你在搜索框里敲下“图书管理系统源代码 java”大概率是因为已经翻了几个 GitHub 仓库发现跑不起来。图书管理系统源代码java版源码数据库文档说明这个东西本质上是一套完整的 Java Web 最小业务闭环前端页面、后端接口、数据库脚本和设计文档一次配齐。它不是什么图书馆行业软件而是用来练会 Java 从业者最该熟的那几件事增删改查、会话管理、事务控制、部署排错。这套源码最适合两类人一是毕业设计和课程设计急着交差的在校生二是简历里需要一个“能跑、能讲、能改”的练手项目的初级开发。2. 技术栈选型与数据库设计为什么教科书都爱拿这个 java 项目当范本2.1 三种常见技术路线怎么选拿到一个图书管理系统源代码包第一件事就是看技术栈。市面上的 Java 版图书管理系统翻来覆去就三条路线JSP Servlet JDBC 的经典三层结构SSMSpring SpringMVC MyBatis以及 Spring Boot MyBatis。三者没有绝对好坏全看你的使用场景。如果是课程设计交差我一般优先推荐 JSP Servlet JDBC。这套组合最贴近 Java 基础课的教学顺序答辩时老师问“你这个请求怎么从页面到数据库的”你能把 Servlet 生命周期、JDBC 连接步骤、request 和 response 的流转讲清楚这就够了。SSM 是过去十年企业里最常见的面试题来源现在很多培训机构还在用它做教学项目优点是分层清晰、便于扩展缺点是配置繁琐初学者在 applicationContext.xml 里折腾半个月都起不来服务。Spring Boot 版本写起来最舒服自动化配置减少了一大堆样板代码但课设老师有时会觉得“太新了不像你自己做的”答辩时穿透力反而弱。从面试角度看图书管理系统几乎囊括了 Java 面试题里必考的最小集合数据库增删改查、PreparedStatement 防 SQL 注入、事务提交回滚、Filter 做登录拦截、分页查询。把这三条路线放到同一张表里看选型逻辑就很直观了技术路线上手难度运行环境面试价值适合场景JSP Servlet JDBC低JDK 8 Tomcat 8.5 MySQL 5.7中侧重 Java 基础课程设计、入门练手SSM高JDK 8 Tomcat 8.5 MySQL 5.7高企业主流框架简历项目、面试准备Spring Boot MyBatis中JDK 8 内嵌 Tomcat中高偏工程化自学进阶、毕设快速出活如果你手头那个源码包是 JSP Servlet 写的别急着嫌弃它老。Java 基础不牢固的时候直接上 Spring Boot你会连“它到底帮我省了什么”都说不清楚。先跑通老项目再谈框架。2.2 功能边界图书管理系统的核心闭环是什么一个合格的图书管理系统源码功能上不会太发散。常见的最小闭环包含八个功能点管理员登录、图书分类管理、图书信息维护、图书检索按书名/作者/出版社模糊查询、读者信息管理、借书、还书、借阅记录查询。热闹一点的还会加公告栏、统计报表、Excel 导出但这些属于加分项不是核心。为什么教科书选这个题目因为图书管理系统的业务逻辑足够简单却又能覆盖一个 Web 系统所有关键环节。借书这个动作牵扯到库存检查、读者状态检查、借阅记录插入、库存字段更新——这是一个典型的事务场景还书动作则要反向回补库存并更新借阅记录状态登录功能牵扯到 Session 管理检索牵扯到 SQL 的 LIKE 查询和分页。换句话说图书管理系统就是一个“麻雀虽小五脏俱全”的 Java 基础综合练习题。这里还要说一句最近搜“图书管理系统 python”的人也很多Python 版用 Django 或 Flask 写起来确实快但 Java 版的价值在于面试时会被追问底层JDBC 连接谁关闭的、事务边界画在哪、连接池参数怎么设。这些追问在 Python 版里很少出现。所以如果你是为了找工作而不是单纯交作业老老实实啃 Java 版别绕路。2.3 数据库先行book、reader、borrow 三张表与字段关系图书管理系统源代码包里数据库脚本通常是核心资产。绝大多数项目都会给一个 library.sql 或者 db_book.sql你需要先把表结构看懂再动手导数据。设计上没有太多花活三张主表加一张管理员表就够了图书表book、读者表reader、借阅表borrow、管理员表admin。图书表至少要存这些字段图书编号、书名、作者、出版社、价格、库存数量、分类、存放位置。读者表要有读者编号、姓名、电话、可借数量、状态正常/冻结。借阅表是关联表记录哪本书被哪个读者借走了什么时候借的、什么时候还的、当前状态是在借还是已还。管理员表就是登录账号和密码。CREATE DATABASE IF NOT EXISTS db_library DEFAULT CHARSET utf8mb4; USE db_library; CREATE TABLE book ( b_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 图书编号, b_name VARCHAR(100) NOT NULL COMMENT 书名, b_author VARCHAR(50) DEFAULT NULL COMMENT 作者, b_publisher VARCHAR(100) DEFAULT NULL COMMENT 出版社, b_price DECIMAL(10,2) DEFAULT 0.00 COMMENT 价格, b_stock INT NOT NULL DEFAULT 0 COMMENT 库存总量, b_remain INT NOT NULL DEFAULT 0 COMMENT 当前可借数量, b_category VARCHAR(50) DEFAULT NULL COMMENT 分类, b_location VARCHAR(50) DEFAULT NULL COMMENT 书架位置 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书表; CREATE TABLE reader ( r_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 读者编号, r_name VARCHAR(50) NOT NULL COMMENT 姓名, r_phone VARCHAR(20) DEFAULT NULL COMMENT 联系电话, r_max_borrow INT DEFAULT 5 COMMENT 最大可借数量, r_status TINYINT DEFAULT 1 COMMENT 1正常 0冻结 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT读者表; CREATE TABLE borrow ( id INT PRIMARY KEY AUTO_INCREMENT, b_id INT NOT NULL COMMENT 图书编号, r_id INT NOT NULL COMMENT 读者编号, borrow_date DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 借书时间, due_date DATETIME DEFAULT NULL COMMENT 应还时间, return_date DATETIME DEFAULT NULL COMMENT 实际还书时间, status TINYINT DEFAULT 0 COMMENT 0在借 1已还 2逾期 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT借阅记录表;这段建表 SQL 是我按最常见的方案补全的实际拿到手的脚本可能字段名不同但逻辑大体一致。关键要看两点一是图书表里有没有把“库存总量”和“当前可借数量”分开分开才能支撑借书时只减 b_remain、补货时只加 b_stock 的灵活逻辑二是借阅表里有没有 status 字段没有的话还书就只能物理删除记录历史借阅全丢了。字段设计上有两个容易踩的坑第一借阅记录里的 b_id 和 r_id 到底要不要建外键约束。教科书项目里往往有但实战里我一般只建普通索引逻辑外键不建物理外键。因为一旦建了物理外键删除一本有借阅历史的书时会直接报约束错误课设答辩现场翻车率极高。第二读者状态字段用 TINYINT 比用 VARCHAR 更省空间0 和 1 的判断也不容易写错字符串。数据库设计阶段多花半小时后面所有代码都跟着顺。3. 把源码跑起来IDEA Tomcat MySQL 环境初始化步骤3.1 版本匹配是第一道坎JDK 8、Tomcat 8.5、MySQL 5.7 是最稳组合拿到源码先别急着导入 IDEA先看一眼项目的发布时间。绝大多数“图书管理系统源代码 java 版”项目的生命周期都很长代码可能是八年前写的但你现在电脑上装的可能是 JDK 17 和 MySQL 8.0。版本不匹配是这类老项目跑不起来的头号原因。老 Java Web 项目最稳的运行组合是 JDK 8 Tomcat 8.5 MySQL 5.7。JDK 11 以上跑老项目时JavaEE 相关的模块被移除JSP 编译经常报找不到 javax.servlet 相关类Tomcat 10 之后更是把包名从 javax.servlet 换成了 jakarta.servlet老源码导入后直接编译失败这问题基本无解只能换 Tomcat 版本。MySQL 8.0 则有两个坑一是驱动类名变了二是连接 URL 需要额外加时区参数。所以我的习惯是用一个固定的开发环境目录专门跑这类老项目。JDK 8 装一份、Tomcat 8.5 解压一份、MySQL 5.7 装一份平时不升级、不乱动。这听起来很保守但能省下大量排查环境问题的时间。3.2 导入数据库用 source 命令而不是复制粘贴数据库初始化是整套流程里最不该出错的一步。很多人从压缩包里解压出 .sql 文件后直接打开复制全部内容粘贴到 Navicat 的查询窗口执行。这种做法遇到带中文注释或者特殊字符的脚本很容易中途报错而且你根本不知道执行到哪一行停下来的。更稳妥的做法是用命令行 source 命令导入。mysql -uroot -p输入密码后进入 MySQL 命令行接着执行CREATE DATABASE IF NOT EXISTS db_library DEFAULT CHARSET utf8mb4; USE db_library; SOURCE D:/course/library.sql;如果 source 路径有中文或者空格导入前先把 .sql 文件复制到盘符根目录比如D:/library.sql避免编码和路径解析出问题。导入完成后用SHOW TABLES;确认表已经建出来再用SELECT * FROM book;确认数据不为空。这两个命令能帮你把“数据库没导成功”和“项目代码有问题”两个阶段隔离开。如果 SHOW TABLES 能看到 4 张左右的核心表那么数据库这块就算过了。注意看建表语句里是否用了 utf8mb4 而不是 gbk这决定了后面页面是不是会出现中文乱码。3.3 配置连接参数url 里的几个参数一个都不能少数据库导入成功之后下一步是改项目里的数据库连接配置。老项目里这个配置的位置不固定常见的有src/jdbc.properties、src/druid.properties、WEB-INF/classes/db.properties也可能是写死在 DAO 工具类里的一个getConnection()方法。你搜索jdbc:mysql就能定位到。针对 MySQL 5.7典型的配置是这样jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/db_library?characterEncodingUTF-8useSSLfalse jdbc.usernameroot jdbc.password123456如果你的开发机装的是 MySQL 8.0那驱动类名和 URL 都要改jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/db_library?characterEncodingUTF-8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password123456这两个配置的区别是血泪经验的总结。MySQL 8.0 官方把驱动类名从com.mysql.jdbc.Driver改成了com.mysql.cj.jdbc.Driver如果只改驱动不改 URL会报Public Key Retrieval is not allowed如果 URL 里不加serverTimezone会报时区错误因为新版驱动默认要求指定时区。另外characterEncodingUTF-8必须放在参数前面否则即使代码里设置了编码数据库连接这层还是会把中文存成问号。如果项目用的是 druid 连接池配置文件的maxActive我一般设 20initialSize设 5minIdle设 5maxWait设 60000。这套参数对课设项目的访问量来说绰绰有余也能在答辩时回答“为什么这么设”这类问题。3.4 在 IDEA 里部署 Tomcatwar exploded 和 Application context数据库配置改完之后老项目在 IDEA 里的部署也有一堆暗坑。很多人的源代码包是 Eclipse 工程结构导入 IDEA 时需要选Import Project而不是直接 OpenIDEA 会自动识别 Eclipse 的.classpath和.project文件。部署时最关键的一步是 Artifact 配置。IDEA 里打开Project Structure在 Artifacts 里点加号选 Web Application Exploded把项目模块加进去。注意选 Exploded 而不是 Archive因为 Exploded 模式下每次修改 JSP 或 Java 文件都能热更新改完刷新页面就能看到效果不必反复重启 Tomcat。然后配置 Tomcat 运行环境。Run Configurations 里点加号选 Tomcat Server LocalApplication Server 选到你解压的 Tomcat 8.5 目录Deployment 选项卡里把刚才建好的 Artifact 加进去Application context 填/library。这里有个高频翻车点Application context 默认会带一个很长的路径如果你代码里登录跳转写的是/library/login.jsp而 context 是空或者别的名字跳转就会 404。保险做法是项目里全局搜索/library/确认这个路径前缀和你配的 context 一致。启动 Tomcat 后访问http://localhost:8080/library/看到登录页环境初始化就算完成了。如果看到 404基本就是 Artifact 没部署成功按第 5 章里的排查顺序逐条过一遍。4. 核心功能落地借书还书背后的增删改查关键代码4.1 登录与会话控制一个 Filter 管住所有受保护页面跑通环境之后就该开始读代码了。图书管理系统源码的价值不在于你能把它跑起来而在于你能讲清楚每一个功能背后的实现逻辑。先从登录开始。大多数项目的登录逻辑是同一个套路登录表单提交到 LoginServletServlet 查数据库里的 admin 表查到了就把用户名写进 session然后重定向到主页查不到就返回错误提示。真正的关键在于怎么保护那些必须登录才能访问的页面。如果每个页面都在 Servlet 里判断 session代码会重复到你怀疑人生。正解是写一个 Filter。WebFilter(/pages/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(); Object admin session.getAttribute(admin); if (admin null) { // 没有登录跳回登录页 resp.sendRedirect(req.getContextPath() /login.jsp); return; } // 已登录继续放行 chain.doFilter(request, response); } }这个 Filter 解决了“哪些页面不能匿名访问”的问题。注意两点一是路径映射用了/pages/*把后台的 JSP 全放在 pages 目录下登录页放在根目录这样只用一条映射就把受保护页面圈住了二是过滤器里拿到的是 session 里的 admin 对象而不是只判断用户名是否为空因为有些项目允许注销后 session 还存在但用户名被清空判断对象本身更保险。4.2 图书检索模糊查询加 PreparedStatement 防注入图书检索是图书管理系统里最常被面试官拷打的点。功能要求很简单输入关键字按书名模糊搜索结果分页展示。但实现细节里的坑不少。public ListBook searchBooks(String keyword, int page, int pageSize) { ListBook list new ArrayList(); String sql SELECT * FROM book WHERE b_name LIKE ? LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ps.setInt(2, (page - 1) * pageSize); ps.setInt(3, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Book book new Book(); book.setBId(rs.getInt(b_id)); book.setBName(rs.getString(b_name)); list.add(book); } } } catch (SQLException e) { e.printStackTrace(); } return list; }这段代码里最容易讲错的地方有两个。第一个是 LIKE 语句里的通配符位置% keyword %必须写在 setString 的参数里而不是直接拼接进 SQL 字符串。一旦你把 keyword 直接拼进 sql用户输入%或者_就会改变查询语义更严重的是可以注入 OR 11之类的内容把整张表拖出来。第二个是分页的 LIMIT 参数第一页从 0 开始所以(page - 1) * pageSize很多初学者写成了page * pageSize结果第一页就少了一条数据。4.3 借书逻辑库存检查加事务两行 SQL 必须一起成功借书是整个项目里事务控制的展示窗口。这个操作涉及两件事插入一条 borrow 借阅记录同时把 book 表里对应图书的 b_remain 减一。任何一个成功一个失败数据就对不上了。public boolean borrowBook(int bookId, int readerId) { Connection conn null; PreparedStatement psUpdate null; PreparedStatement psInsert null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 先检查库存 String checkSql SELECT b_remain FROM book WHERE b_id ? FOR UPDATE; // 再减库存 String updateSql UPDATE book SET b_remain b_remain - 1 WHERE b_id ? AND b_remain 0; psUpdate conn.prepareStatement(updateSql); psUpdate.setInt(1, bookId); int rows psUpdate.executeUpdate(); if (rows 0) { conn.rollback(); return false; // 库存不足 } // 插入借阅记录 String insertSql INSERT INTO borrow (b_id, r_id, borrow_date, due_date, status) VALUES (?, ?, NOW(), DATE_ADD(NOW(), INTERVAL 30 DAY), 0); psInsert conn.prepareStatement(insertSql); psInsert.setInt(1, bookId); psInsert.setInt(2, readerId); psInsert.executeUpdate(); conn.commit(); // 全部成功才提交 return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ignored) { } } e.printStackTrace(); return false; } finally { DBUtil.close(conn, psUpdate, psInsert); } }这里有几个最重要的参数设定。setAutoCommit(false)是事务的开关不写这句连接默认是自动提交两条 SQL 之间出了错前一条已经生效后一条不会回滚。WHERE b_id ? AND b_remain 0这个条件同时充当了“库存检查”和“库存扣减”两个角色用一条 UPDATE 语句完成检查加更新比先 SELECT 再 UPDATE 更安全因为它天然防止了并发场景下的超借问题。DATE_ADD(NOW(), INTERVAL 30 DAY)把默认借期设为 30 天这是最常见的惯用值需要改借期的话把这个参数暴露到管理页面。4.4 还书逻辑状态变更与库存回补还书和借书是对称的但逻辑更简单把 borrow 表里对应记录的状态改成已还同时把 book 表里的 b_remain 加一。关键在于定位“对应记录”——不能只按 bookId 和 readerId 找因为同一本书可能被同一个读者借过多次。正确写法是带上状态条件只更新 status 0 的在借记录。String updateBorrow UPDATE borrow SET status 1, return_date NOW() WHERE b_id ? AND r_id ? AND status 0; String updateBook UPDATE book SET b_remain b_remain 1 WHERE b_id ?;两行 SQL 同样要放在事务里执行。这里有个很少人注意到的细节b_remain b_remain 1不能写成b_remain 1 b_remain后者是赋值语句的常见笔误。如果项目里有逾期罚款功能status 字段还需要区分“1已还”和“2逾期”在还书时判断 return_date 是否晚于 due_date再决定把 status 更新为 1 还是 2。这四段代码基本就是图书管理系统后端的所有核心。你把这个借书加还书的闭环背熟了面试官追问事务、并发、SQL 注入你都能从这几段代码里找到答案。5. 运行避坑手册从 404 到中文乱码的 5 个高频踩坑点5.1 现象Tomcat 启动成功但访问项目首页报 404Tomcat 在 IDEA 控制台里显示“Server startup in 5000 ms”说明服务起来了但浏览器访问项目路径就是 404。这个坑在部署老项目时出现频率极高原因多半不是代码而是 IDEA 的 Artifact 部署配置。原因项目 Artifact 没有把依赖 jar 包同步输出到 Tomcat 的 WEB-INF/lib 目录下。JSP 页面能编译但编译后找不到项目依赖的 servlet-api、jstl、mysql 驱动等 jar 包Tomcat 就会给你一个干净的 404 页。另一个常见原因是 Deployment 里根本没有把 Artifact 加进去Tomcat 启动的是一个空壳。解决打开 Project Structure - Artifacts确认是你创建的war exploded: 项目名 Web exploded而不是空的“Web Application: Archive”。然后在 Run Configurations - Deployment 里把那个 Artifact 添加进去Application context 填/library。如果页面引用了静态资源css、js还要检查这些文件是否被打进了 Artifact 的 Output LayoutIDEA 有时候不会自动带上 src/main/webapp 之外的外部资源目录。5.2 现象连接数据库报 Communications link failure 或 Unknown databaseTomcat 能起来页面也能打开但一登录就报错Communications link failure、Unknown database db_library或者Access denied for user root。这一串错误信息指向三个不同层面分别排查。原因Communications link failure是 MySQL 服务没启动或者驱动访问了错误的端口默认 3306 被占用Unknown database是你根本没有执行建库脚本或者库名拼写不一致Access denied是用户名密码不对也可能是 MySQL 8.0 的认证插件与老驱动不兼容。解决先用mysql -uroot -p在命令行确认数据库能连上再用SHOW DATABASES;看库是否存在。然后回项目里核对 jdbc.properties 的库名很多源码包里的库名是library或book_manage而不是你想当然的db_library。库名对上了还报连接失败就看 URL 里的 IP 是不是 localhostMySQL 8.0 的认证方式如果用了 caching_sha2_password老版本驱动连不上得在 MySQL 里执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码;降级认证方式。5.3 现象页面显示成一大片问号和乱码能登录、能查询、能管理但中文书名和读者姓名在页面上全部显示为?或者乱码。这个问题出现的时机经常是你刚换了一台新电脑重跑旧项目原来好好的换环境就乱码了。原因三层环节里至少一层编码不一致。数据库表是 utf8mb4项目代码里设置的是 GBK数据库连接 URL 里没有characterEncodingUTF-8JSP 页面没有指定pageEncoding。这三层只有全部统一成 UTF-8中文才能正常显示。解决三层逐一排查。连接 URL 加characterEncodingUTF-8检查源文件的编码IDEA 右下角确认所有 .java 和 .jsp 文件用的是 UTF-8确认建表语句里DEFAULT CHARSET utf8mb4。如果项目里已经有大量 GBK 编码的文件且不方便转换可以在 web.xml 里加一个编码过滤器强制把 request 和 response 的编码设为 UTF-8这是最常见的全局补救办法。要记住一个顺序编码要在 filter 里最先执行放在其他过滤器之前否则请求参数已经被读过了。5.4 现象Tomcat 启动时端口被占用启动 Tomcat 时控制台报Port 8080 is already in use或者访问页面时连到完全无关的另一个应用上。这在装了多个开发环境的机器上特别常见。原因不只是“另一个 Tomcat 在运行”这一种可能IDEA 自带的某个插件、之前没有正常关闭的 Tomcat 进程、甚至其他开发工具的调试服务都可能占着 8080。Windows 上最常见的元凶是 PID 对应的 java.exe 进程没有被释放。解决先确认谁占用了端口netstat -ano | findstr 8080看到最后一列 PID 后在任务管理器里找到对应进程并结束。如果这个进程是之前异常退出的 Tomcat 残留直接杀掉没问题。如果确认 8080 被其他常驻服务占用也可以给当前 Tomcat 换个端口在 Tomcat 的conf/server.xml里把 Connector port 从 8080 改成 8081但记得同时改 IDEA Run Configuration 里的 HTTP port改完重启才生效。5.5 现象删除图书或读者时报外键约束失败在管理页面删除一条图书记录系统报Cannot delete or update a parent row: a foreign key constraint fails。这个报错很吓人但其实问题出在表结构设计时埋的雷。原因borrow 表里的 b_id 或 r_id 建了物理外键而 borrow 表里还保留着这条记录的历史借阅数据。你要删的图书曾经被借出过借阅记录还没有清理数据库当然不允许你删主表记录。解决在管理后台删除图书和读者前先删除或标记 borrow 表里对应的历史记录例如DELETE FROM borrow WHERE b_id ?;然后再删 book 表里的数据。更符合实际业务的做法是逻辑删除给 book 表和 reader 表各加一个 del_flag 字段删除操作改为执行UPDATE book SET del_flag 1 WHERE b_id ?;所有查询语句里默认过滤del_flag 0。这样借阅历史完整保留删除操作不会破坏外键关系还保留了“误删恢复”的后路。逻辑删除是正规项目里的标准做法也是对这套源码最高性价比的改造但注意它意味着所有相关的 SQL 查询都要加上过滤条件改动量不小适合放在跑通之后再动手。6. 最值的进阶改造给图书管理系统补上面试官一眼就爱的三个细节项目跑通只是起点真正拉开差距的是你在这套源码之上做了哪些“安全性和工程质量”层面的改造。我自己带过的初级开发里能把这套图书管理系统讲出彩的几乎都做了下面三件事。第一件是把所有散落的 JDBC 代码换成 MyBatis 的 Mapper 接口。不用 Spring Boot就在原来的 Servlet 项目里引入 MyBatis 依赖把 SQL 从 Java 代码里抽出来放到 XML 文件里。这个改造的价值在于原来一个searchBooks方法里的 SQL 写死在 Java 里改成 Mapper 后你能在答辩时说清楚“SQL 与业务代码分离”这个设计原则。改造量不大但讲出来非常加分。第二件是给管理员密码加盐哈希。原项目几乎清一色明文存密码你只需要在登录和注册逻辑里调用一个 MD5 工具类存库时用MD5(password salt)其中 salt 在用户创建时随机生成并存入独立字段。这是安全意识的直接体现。第三件是给所有查询条件加上参数校验和分页硬上限比如 pageSize 超过 100 就强制改成 100防止有人把全表数据一次性拖走。这三件事做完这个项目从“课设水平”直接变成“可以写进简历的项目”面试官追问的每一个点你都能从一个真实的改造经历出发去回答。前几年我带一个应届生改这套系统他把借书功能里的事务注解写错了位置导致扣库存和插记录永远不在同一个事务里。当时排查了很久最后发现他对“事务边界”这个概念的理解是空的。后来我把借书方法改成手写 setAutoCommit 的方式让他重做一遍他一下子就懂了。我的建议是第一遍跑通异常第二遍跳出舒适区、动手改成 MyBatis 加安全防护改完这个方向基本就吃透了。这是一条比刷十道 java 面试题更有效的路希望帮到你。本文还有配套的精品资源点击获取