ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Java+MySQL图书管理系统课设:从建表到事务的完整落地指南

Java+MySQL图书管理系统课设:从建表到事务的完整落地指南 简介这是一份基于 Java 与 MySQL 实现的图书管理系统完整源码包适用于高校期末大作业、课程设计及毕业设计参考。系统覆盖图书信息管理、借阅归还、读者管理等常见业务模块代码注释详细结构清晰新手也能较快上手。压缩包共 93 个文件约 3.11MB其中包含 40 个 Java 源文件、14 个 JSP 页面、11 个 JS 脚本、8 个 XML 配置、4 个 CSS 样式以及 SQL 数据库脚本、README 说明文档、项目依赖 jar 包和 IDE 工程配置方便导入后直接部署运行。资源还附有文档说明可帮助理解数据库表设计和前后端调用关系适合作为高分课设的参考模板。目前已有 65 人学习下载对于希望快速交付一个功能完整、界面简洁的图书管理系统的同学来说是一份可直接落地的参考资料。1. 图书管理系统不是 CRUD是“说话”的系统很多交上去的基于 Java 和 MySQL 的图书管理系统跑是能跑偏偏在“答辩当场”跑挂。真正常见的问题不是缺界面而是缺一致的数据、拿不出手的事务边界、一被问就沉默的表结构。图书管理系统的课设核心从来不是把增删改查拼出来而是把“数据库为什么这样建、库存扣减为什么不能只写一条 UPDATE、借阅记录怎么保证不脏”讲清楚。这篇文章直接按“建表 - 配参数 - 写登录与借书还书 - 结课验收”的顺序展开每一段都可以直接落成源码和文档里的一节也方便在答辩现场当场演示。2. 用 Java MySQL 落地前先把图书管理系统的三张表定下来2.1 三张表和一个外键图书管理系统的最小数据模型无论源码里用 JSP 还是 Spring BootJava 操作 MySQL 最终都落到几张表上。我一般不看别人的完整脚本而是按“管理员、图书、借阅记录”三个核心实体倒推。管理员表存登录凭据和角色图书表存库存和基本书目信息借阅表记录谁借了哪本书、借了多久、有没有还。最小可用的建表脚本如下CREATE TABLE admin ( id BIGINT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(255) NOT NULL COMMENT BCrypt密文, role TINYINT NOT NULL DEFAULT 1 COMMENT 1普通管理员9超管, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE book ( id BIGINT NOT NULL AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL, title VARCHAR(200) NOT NULL, author VARCHAR(100) DEFAULT NULL, stock INT NOT NULL DEFAULT 0, INDEX idx_title (title), INDEX idx_isbn (isbn), PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE borrow ( id BIGINT NOT NULL AUTO_INCREMENT, book_id BIGINT NOT NULL, user_id BIGINT NOT NULL, borrow_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, due_time DATETIME NOT NULL, return_time DATETIME DEFAULT NULL, status TINYINT NOT NULL DEFAULT 1 COMMENT 1在借2已还3逾期, PRIMARY KEY (id), KEY idx_book (book_id), KEY idx_user (user_id), CONSTRAINT fk_borrow_book FOREIGN KEY (book_id) REFERENCES book (id), CONSTRAINT fk_borrow_user FOREIGN KEY (user_id) REFERENCES admin (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这个模型里有几个细节值得写进课设的数据库设计说明。第一password字段用varchar(255)而不是定长varchar(32)因为登录密码不应该存明文或简单 MD5常见做法是拿 BCrypt 散列后入库散列结果一般在 60 个字符左右。第二所有表引擎都用 InnoDB因为借书和还书需要在一个事务里同时修改库存和借阅记录MyISAM 不支持行级锁和事务回滚库存很容易在并发场景下被扣成负数。第三book表的idx_title和idx_isbn不是摆设答辩时老师常问“搜索一本书走的是哪个索引”这两条索引就是回答的抓手。2.1.1 借阅表的时间字段DATETIME 与 TIMESTAMP 二选一borrow表里我统一用 DATETIME不碰 TIMESTAMP。原因很实际TIMESTAMP 的范围到 2038 年而且会受到 MySQL 会话时区影响和 Java 侧的serverTimezone叠加后容易把时间整体算偏DATETIME 存的是字面值只要连接串指定了serverTimezoneAsia/Shanghai查询和展示的偏差就小。如果哪份源码把时间字段设计成 VARCHAR那排序和逾期判断一定有问题不建议照着抄。2.1.2 外键课设建议保留生产环境倒是可以砍borrow表上的物理外键在真实互联网项目里我通常会去掉因为高频写入时外键校验会成为瓶颈但课设恰恰相反保留外键能让 E-R 图和表结构说明更直观直接用数据库约束来保护数据完整性。答辩被问到“删了图书怎么办”可以直接回答“外键约束会阻止删除有未还记录的书”比在 Service 层写一堆兜底代码更清晰。2.2 用一条递归 INSERT 给图书管理系统预灌 100 条测试数据答辩现场最尴尬的事是打开系统书架上一共只有三本书。我一般会在建库脚本里顺手写一段造数 SQL用 MySQL 8.0 的递归 CTE 一次性灌入 100 本书INSERT INTO book (isbn, title, author, stock) WITH RECURSIVE seq (n) AS ( SELECT 1 UNION ALL SELECT n 1 FROM seq WHERE n 100 ) SELECT CONCAT(978712, LPAD(n, 6, 0)), CONCAT(Java 实战教程 第, n, 卷), CONCAT(作者, n), FLOOR(1 RAND() * 8) FROM seq;WITH RECURSIVE先生成 1 到 100 的序列再通过CONCAT拼出 ISBN、书名FLOOR(1 RAND() * 8)生成 1 到 8 的随机库存。这条语句的优点是幂等性容易控制删掉book表数据后可以反复执行。如果课设环境是 MySQL 5.7没有递归 CTE就改成存储过程循环插入或者用多个INSERT INTO ... VALUES批量执行。建议把造数 SQL 写进文档的“测试数据准备”小节评审老师现场打开系统看到大量数据第一印象会好很多。3. 本地把 Java 和 MySQL 配到能跑最小工程与数据源参数3.1 mysql安装配置教程里最容易被跳过的两步PATH 和建库命令做这个课设的第一件事不是写代码而是确认mysql和java -version能在命令行里直接执行。Windows 下装了 MySQL 却发现代码连不上本地库多半是环境变量里没加bin目录。我一般在安装完成后会做两件事mysql --version mysql -u root -p -e CREATE DATABASE library DEFAULT CHARACTER SET utf8mb4;第一行验证客户端能被调用第二行用-e参数直接执行建库命令不需要先进交互式 shell。DEFAULT CHARACTER SET utf8mb4是为了避免中文乱码图形化工具通常默认生成utf8但utf8mb4才能完整支持生僻字和 Emoji。建好库之后再导入表结构mysql -u root -p library schema.sql3.1.1 当“mysql 不是内部或外部命令”出现时报这个错就是 PATH 没配到 MySQL 的bin目录。常见做法是到安装目录找到bin文件夹的完整路径加进系统环境变量然后新开一个命令行窗口再跑mysql --version。这步不做后面的所有连接失败都会像数据源配置问题实际是命令根本没被找到。3.2 最小 Spring Boot 工程pom 里只放 3 个 starter虽然课设的经典方案里有 JSP 加 Servlet但我更建议用 Spring Boot理由很直接配置少代码短答辩时被追问“连接数据库的过程”也容易讲清楚。最小工程只需要引入三个依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId version2.7.18/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependencyspring-boot-starter-web提供内嵌 Tomcat写完 Controller 直接启动不用手工打包 warmysql-connector-j是新版本 MySQL 驱动的坐标老项目用的mysql-connector-java也可以但新工程直接用新版驱动省去后续兼容问题mybatis-spring-boot-starter会把 SQL 写到 XML 文件里便于把每一条查询都放进文档说明。如果只用“登录加增删改查”不引入 JPAMyBatis 的 SQL 可控性在答辩时更好讲。3.3 数据源参数表URL 里每个参数都值得背熟光有依赖还连不上 MySQL关键在application.yml的连接串。我见过太多次漏掉参数导致中文乱码或时区报错的情况最稳的配置是这样spring: datasource: url: jdbc:mysql://localhost:3306/library?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mappers/*.xml configuration: map-underscore-to-camel-case: trueURL 里每个参数的作用可以参考下面这张表。它们不一定出现在每篇 mysql 安装配置教程里但课设排错时最常用到参数作用不加会怎样useUnicodetrue启用 Unicode 字符映射中文参数读取异常characterEncodingutf8指定客户端字符集写入的书名显示成问号serverTimezoneAsia/Shanghai指定服务器时区报 Server returns invalid timezoneuseSSLfalse本地连接关闭 SSL控制台刷出大量 SSL 警告allowPublicKeyRetrievaltrue允许获取 RSA 公钥MySQL 8 报公钥检索错误map-underscore-to-camel-case: true把数据库的borrow_time自动映射成 Java 属性borrowTime实体类里就不用写满TableField注解。项目启动后发现连不上库先按这张表逐项核对 URL再考虑代码问题顺序反了会浪费很多时间。4. 登录、分页、借阅事务图书管理系统最容易答辩翻车的四段代码4.1 借书/还书的事务实现先库存判断再写借阅记录十个图书管理系统里有八个把借书写成两条独立 SQL中间没有任何事务保护。借书的正确逻辑应该是先查库存再更新剩余库存最后插入借阅记录三步中任何一步失败都要整体回滚。用 Spring 的Transactional可以把这个边界压进同一个方法里Service public class BorrowService { private final BookMapper bookMapper; private final BorrowMapper borrowMapper; public BorrowService(BookMapper bookMapper, BorrowMapper borrowMapper) { this.bookMapper bookMapper; this.borrowMapper borrowMapper; } Transactional(rollbackFor Exception.class) public boolean borrowBook(Long bookId, Long userId) { Book book bookMapper.selectByIdForUpdate(bookId); if (book null || book.getStock() 0) { return false; } bookMapper.decreaseStock(bookId); return borrowMapper.insert(bookId, userId, LocalDateTime.now(), LocalDateTime.now().plusDays(30)) 0; } }这段代码的核心在selectByIdForUpdate。FOR UPDATE会锁住book表里idbookId的这一行其他会话再对同一本书调用借书操作时会等待锁释放直到当前事务提交。这是典型的悲观锁把同一本书的借出操作变成串行执行避免两个管理员同时借出最后一本库存。数据库课里常讲的超卖问题放到图书管理系统里就是两个人同时借唯一一本库存事务没有锁时会造成库存变成 -1。4.1.1 为什么不用“先 UPDATE 后判断影响行数”也有人会说直接写UPDATE book SET stock stock - 1 WHERE id #{bookId} AND stock 0通过影响行数判断是否扣减成功不也能防超卖吗这个办法确实挡住了负库存但它只解决了扣减解决不了“借阅记录插入失败时库存已经扣了”的问题。把 UPDATE 和 INSERT 放进同一个Transactional任何一步抛异常两条语句都回滚库存才不会平白少一本。还书逻辑同理UPDATE borrow SET status 2, return_time NOW()和UPDATE book SET stock stock 1必须成对提交。4.2 SQL 注入防线MyBatis 的 #{} 和 ${} 用错就当场出局图书管理系统的搜索功能免不了按书名查书如果直接用字符串拼接用户在搜索框输入1 OR 11就能把整张表带出来。MyBatis 里#{}是预编译占位符${}是直接拼接 SQL搜索查询的 XML 应该这样写select idsearchBooks resultTypecom.example.library.entity.Book SELECT id, isbn, title, author, stock FROM book WHERE title LIKE CONCAT(%, #{keyword}, %) ORDER BY id DESC /select这里有个常见错误看到 LIKE 查询要拼%就直接写成WHERE title LIKE %${keyword}%这是答辩老师最喜欢抓的注入点。改成CONCAT(%, #{keyword}, %)之后效果一样但参数值会走 PreparedStatement不会打破原有 SQL 结构。如果确实要用${}做动态排序字段必须做白名单校验不能直接把外部参数拼进 ORDER BY。4.3 课设高频报错排查表看报错字面去改配置本地连不上 MySQL 时控制台报错已经能指出大概方向。整理一份我排过多次的对照表源代码和文档里放一份排错能省很多时间报错片段直接原因处理Public Key Retrieval is not allowedMySQL 8 默认认证插件为 caching_sha2_passwordURL 后加allowPublicKeyRetrievaltrueUnknown initial character set index 255字符集参数冲突删除 characterEncoding 参数确认服务端为 utf8mb4Server returns invalid timezone时区未指定加serverTimezoneAsia/ShanghaiTable library.book doesnt existLinux 下表名大小写敏感表名统一用小写Access denied for user root密码或权限不对先用mysql -u root -p验证账号密码这几类问题都发生在 JDBC 驱动层URL 改正后基本立即生效。如果改了 URL 还报同样的错重启项目再看因为连接池里的旧连接不一定马上释放。遇到Access denied时先回命令行确认账号密码再查代码不要一上来就改连接串。4.4 分页查询结果为 List不是 PagePageHelper 的完整流程图书列表不写分页数据超过几十条后页面会明显变慢。常见做法是引入pagehelper-spring-boot-starter在查询前调用PageHelper.startPagePageHelper.startPage(pageNum, pageSize); ListBook books bookMapper.pageSearch(keyword); PageInfoBook pageInfo new PageInfo(books);第一行开启分页参数第二行返回的是当前页数据第三行PageInfo里才有total、pages、pageNum等元信息。渲染页面时一定要取pageInfo.list不要直接遍历books因为PageHelper返回的实际上是一个 Page 对象直接当普通 List 用在某些模板引擎里会出现数据展示异常。配置文件里把reasonable: true打开页码超出范围时自动收拢到首页或末页演示时就不会因为手动改 URL 参数页而白屏。5. 结课验收最后一步给图书管理系统补上自动备份与 Java 面试延伸5.1 用 Windows 批处理脚本把 MySQL 数据自动备份打包答辩前夜数据库一旦误操作工作量可能直接归零。我一般会在项目根目录放一个backup.bat用mysqldump导出 SQL 文件再用日期生成文件名echo off set BACKUP_DIRD:\backup\library if not exist %BACKUP_DIR% mkdir %BACKUP_DIR% set FILE_NAMElibrary_%date:~0,4%%date:~5,2%%date:~8,2%.sql mysqldump -uroot -p123456 library %BACKUP_DIR%\%FILE_NAME% echo backup complete: %BACKUP_DIR%\%FILE_NAME%脚本先检查备份目录是否存在不存在则创建然后从系统日期里截取年、月、日拼成library_20250315.sql这样的文件名最后交给mysqldump导出。如果想保留滚动备份可以追加一行forfiles /p %BACKUP_DIR% /m *.sql /d -7 /c cmd /c del path删除 7 天前的旧文件。把这段脚本写进文档说明“数据可恢复”就成了一条能现场验证的加分项。5.2 把课设源码改写成 Java 面试答案的三个延伸点图书管理系统虽然简单把它讲深一点却能覆盖几个高频 java 面试题。第一点是事务隔离级别借书用的FOR UPDATE在默认级别下能锁定已提交的最新数据去掉锁之后两个会话可能同时读到不同快照这就是“当前读”和“快照读”的区别。第二点是索引失效场景搜索查询里写LIKE %关键%时idx_title不会生效因为前导模糊让 B 树无法定位起始范围改成LIKE 关键%才能走索引。第三点是存储过程有些老师喜欢要求加一个“统计月借阅量”可以直接在 MySQL 里建一个存储过程CREATE PROCEDURE monthly_borrow_count(IN p_month CHAR(7)) BEGIN SELECT COUNT(*) AS cnt FROM borrow WHERE DATE_FORMAT(borrow_time, %Y-%m) p_month; END;调用时执行CALL monthly_borrow_count(2025-03);注意p_month的参数格式必须严格匹配%Y-%m例如 2025-03。这段 SQL 同时解释了存储过程的输入参数和结果集返回机制把课设的借阅记录表顺势引到报表统计场景比只写一句“完成了基础功能”更能体现对 MySQL 的理解。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表