ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SpringBoot+MyBatis+MySQL图书管理系统:从建表到部署全解析

2026/9/13 7:54:37 拓冰建站 浏览量
SpringBoot+MyBatis+MySQL图书管理系统:从建表到部署全解析 简介这是一份基于SpringBootMyBatisMavenMySQL的图书管理系统毕业设计项目源码面向需要完成Java毕设、期末大作业或课程设计的高校学生也适合Java初学者通过实际项目掌握SSM开发流程。资源包共40个文件其中31个Java源文件覆盖控制层、业务层与数据访问层代码配套1个SQL数据库脚本、1个YML配置文件、1个XML映射文件以及README说明文档另有3张界面效果图可快速预览运行结果整个压缩包仅1.42MB轻量易部署。项目采用Maven管理依赖SpringBoot自动配置简化开发MyBatis实现数据持久化代码注释到位逻辑清晰即便新手也能快速上手。该项目已通过严格调试个人手打获得98分导师评价高系统功能完整涵盖图书管理、借阅归还、分类查询、读者管理等常见模块界面简洁、操作便利具有很好的实际应用价值。目前已有185人学习下载下载后简单配置数据库即可运行适合直接作为毕业设计、期末大作业或课程设计参考项目使用。1. SpringBoot MyBatis Maven MySQL 图书管理系统毕业设计还能怎么拆一个能跑通的图书管理系统源码很多人拿到手不是先看代码而是先问“数据库密码在哪改”。这其实暴露了毕设项目的真正门槛功能再简单SpringBoot、MyBatis、Maven、MySQL 这条链路只要有一个环节不对服务就起不来。这套基于 SpringBoot MyBatis Maven MySQL 的图书管理系统源码把图书管理、读者管理、借还书流程做成完整闭环附 SQL 脚本、Maven 工程和截图代码带注释适合直接用作毕业设计、期末大作业也适合想快速看懂 Java Web 分层的新手后端开发。它不讲高并发但足够讲清楚一个业务管理系统从建表到接口实现的全部要点。2. 从 SQL 到实体先把 IKUN_library.sql 这张底图看清压缩包解开后典型结构是 Library-master 目录、IKUN_library.sql 脚本和 pom.xml 平铺在一起。Library-master 下面才是 src/main/java、src/main/resourcesimg 目录里放的是运行截图README.md 记录了启动方式。先按这个顺序看别一上来就双击运行。尤其是 SQL 文件很多时候建表语句和初始数据都在里面读懂了它后面的实体类、Mapper 映射才有着落。2.1 先看表结构再想实体类怎么写毕设级的图书管理系统通常不会设计太多表核心就三张图书表book、读者表reader、借阅记录表borrow_record。有些系统会把管理员单独拆一张 admin 表有些直接复用 reader 表中的角色字段。关键在于借阅记录表如何关联 book 和 reader以及状态字段怎么设计。以这套系统的场景来说表结构一般长这样表名主要字段作用bookid, book_no, name, author, publisher, stock, remaining存放图书信息与库存readerid, username, password, phone读者账户信息borrow_recordid, book_id, reader_id, borrow_time, return_time, status记录一次借阅/归还行为book 表里如果同时维护 stock 和 remaining借书时就需要两步先查 remaining 大于 0再在 borrow_record 插入一条状态为“借出”的记录同时 remaining 减 1。return_time 留空归还时填上并改 status。这个模型简单、好答辩也是多数毕设的默认做法。下面是一段典型的图书表建表语句字段命名方式和解压后 SQL 里的风格基本一致create table book ( id bigint auto_increment primary key, book_no varchar(32) not null unique comment 图书编号, name varchar(128) not null, author varchar(64), publisher varchar(128), stock int not null default 0, remaining int not null default 0 ) engineInnoDB default charsetutf8mb4;这里把book_no设成唯一索引是为了避免同一本书重复录入remaining单独维护而不每次 count 借阅记录是为了列表查询时少一次聚合。需要注意stock和remaining的初始值要一样否则系统一上线库存就对不上。外键方面我一般会把外键关系画进 ER 图供评审但在表上不设硬外键因为 MyBatis 批量操作和删除时硬外键容易成为包袱。2.2 导入 MySQL 的两种方式与字符集坑SQL 文件里大概率已经写好 create database 和 use你只需指定 root 用户执行。我常用的方式是命令行导入Windows 和 Linux 都适用mysql -uroot -p -e create database if not exists ikun_library default charset utf8mb4; mysql -uroot -p ikun_library IKUN_library.sql-u指定用户-p提示输入密码-e表示直接执行后面这段 SQL把文件内容重定向进 mysql 客户端。如果 IKUN_library.sql 文件里已经包含建库语句第一条命令可以省略但显式指定字符集更稳妥。导入后马上执行show tables;看表数量再select count(*) from book;看初始数据是否进去。注意MySQL 8 以上默认字符集是 utf8mb4但老脚本可能是 utf8。导入前用head -50 IKUN_library.sql看建表语句里的 CHARSET统一改成 utf8mb4 再导否则遇到 emoji 或特殊符号会报 Incorrect string value 错误。如果你在 Windows 的 CMD 里导入文件路径尽量不要用中文路径否则 mysql 客户端读取文件时可能按系统编码解析失败。还有一个容易被忽略的点SQL 脚本如果带了DROP TABLE IF EXISTS导入前先确认那个库里没有别人的数据这个项目是独立库执行 DROP 没问题。3. 从 pom.xml 到 application.yml把 SpringBoot 与 MyBatis 的装配关系理顺SpringBoot 之所以能少写配置是把数据源、MyBatis 的 SqlSessionFactory 都通过自动配置帮你组装好了。但组装的前提是 pom.xml 里有对应依赖、application.yml 里有正确参数。一旦依赖缺失或配置项拼错报错往往出现在启动时最常见的提示就是 Failed to configure a DataSource。3.1 用 Maven 依赖管理先看 pom.xml 里锁定了什么打开 pom.xml第一件事就是确认三个核心依赖在不在spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-j旧版本是 mysql-connector-java。典型依赖区块如下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency代码里故意不写 version是因为 spring-boot-starter-parent 的父级依赖管理已经统一锁版本直接写在里面反而容易被 MyBatis 与 SpringBoot 版本兼容性坑到。spring-boot-starter-web 提供 MVC 和内嵌 Tomcatmybatis-spring-boot-starter 会注册 SqlSessionFactory 和 Mapper 扫描器mysql-connector-j 是 JDBC 驱动scope 是 runtime编译期不需要。如果你在项目里看到依赖存在但启动说找不到 Mapper通常是 Mapper 接口没有被扫描到下面 3.2 会说到扫描方式。3.2 application.yml 数据源配置与参数说明这个项目是标准的 SpringBoot 单数据源应用配置集中在 application.yml 或 application.properties 里。我一般用 yml层级清晰注释方便。核心配置如下spring: datasource: url: jdbc:mysql://localhost:3306/ikun_library?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.library.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplurl 里的serverTimezoneAsia/Shanghai解决驱动与 MySQL 时区不一致useSSLfalse避免本地开发时 SSL 握手警告。driver-class-name在 MySQL 8 以上用com.mysql.cj.jdbc.Driver老驱动类名少一个 cj启动时容易踩。mapper-locations指向 src/main/resources/mapper 下的 XMLtype-aliases-package让你在 XML 里直接写实体类名而不是全限定名。map-underscore-to-camel-case打开后数据库字段book_no能自动映射到实体属性bookNo。log-impl配成 StdOutImpl 会打印 SQL 和参数调试动态 SQL 很有用线上关掉。3.3 Mapper 接口与 XML 的对应关系以及单字符比较的坑Mapper 接口是这个项目里最容易让新手绕晕的部分。接口定义方法XML 写 SQL两者靠 namespace 绑定。Mapper 接口没有实现类MyBatis 在运行期通过 JDK 动态代理为它生成实现所以接口方法名、参数注解和 XML 的 id 必须严格对应这也是 Java 面试里经常提到动态代理的场景。例如 BookMapper.java 里定义方法Mapper public interface BookMapper { Book selectById(Param(id) Long id); int updateRemaining(Param(id) Long id, Param(delta) int delta); }对应的 XML 用 namespace 指向接口全限定名id 指向方法名update idupdateRemaining update book set remaining remaining #{delta} where id #{id} /updateParam是 MyBatis 的多参数绑定方式不在接口上写会导致 XML 里只能用 arg0/param1可读性很差。如果接口上没加 Mapper你需要在启动类上使用 MapperScan 指定包路径否则运行期注入 BookMapper 会报 No qualifying bean。动态 SQL 是 MyBatis 的常用能力列表查询往往长这样select idselectByCondition resultTypecom.example.library.entity.Book select * from book where if testname ! null and name ! and name like concat(%, #{name}, %) /if if teststatus 1 and stock gt; 0 /if /where /select这里有一个高频踩坑点MyBatis 的 OGNL 表达式里1会被当成字符常量。如果 status 是 Integerstatus 1会在比较时出现类型不一致条件可能永远不成立如果把 status 定义成 Stringstatus 1又能跑通很容易让代码风格失控。最干净的办法是实体里用 Integer 表示状态表达式写status 1不要让 MyBatis 做隐式类型转换。下面这张表可以直接贴进你的笔记表达式status 实际类型建议status 1Integer推荐status 1Integer不推荐OGNL 按字符处理status 1String能用但不规范另一个细节是gt;在 XML 里大于号要转义直接用会报标签闭合错误。看不到 SQL 时可以开 log-impl控制台会打印最终执行的语句排查这类问题很快。另外补充一个关于 MyBatis 缓存的边界二级缓存不是默认开启的。就算现在这个项目用不到也要知道一旦打开缓存结果对象必须实现 Serializable否则序列化缓存时会报 NotSerializableException。答辩时提到这一点能说明你理解缓存机制而不是只调过接口。4. 借阅流程串联从 Controller 到 Service 再落到 MyBatis 查询图书管理系统的核心流程是查书、借书、还书、记台账。代码组织上Controller 只做参数接收和结果返回Service 处理业务规则Mapper 做数据访问。很多毕设的问题在于把业务判断写在 Controller 里答辩被问几句就解释不清。把借阅状态判断全部收进 Service 层代码会干净很多。4.1 RESTful 接口设计与统一返回体接口设计上我通常给前端返回一个统一结构至少包含 code、message、data。这样分页、异常提示都能复用。核心接口大概有几类方法路径作用GET/api/book/list图书列表支持按书名搜索POST/api/book/borrow提交借书请求参数 bookId、readerIdPOST/api/book/return提交归还请求参数 bookId、readerIdGET/api/record/list借阅记录查询Controller 层代码不复杂构造函数注入比字段注入更清晰也方便写单测RestController RequestMapping(/api/book) public class BookController { private final BookService bookService; public BookController(BookService bookService) { this.bookService bookService; } PostMapping(/borrow) public Result borrow(RequestBody BorrowRequest req) { return Result.ok(bookService.borrow(req.getBookId(), req.getReaderId())); } }借书入参用 DTO 接收而不是裸传一堆参数这样接口扩展时不用改方法签名。Result可以做成泛型类data 字段放 null 也能给前端清晰的 JSON 结构。如果项目里已经有一个叫 CommonResult 或 Result 的类直接复用它保持统一。4.2 Service 层用事务锁住库存借书规则不复杂先查书是否存在、剩余量是否大于 0再查读者是否超过最大可借数量都通过后插入借阅记录并扣减库存。插入记录和扣库存必须一起成功或一起失败所以方法上要加 TransactionalTransactional(rollbackFor Exception.class) public Long borrow(Long bookId, Long readerId) { Book book bookMapper.selectByIdForUpdate(bookId); if (book null || book.getRemaining() 0) { throw new BusinessException(图书不存在或库存不足); } BorrowRecord record new BorrowRecord(); record.setBookId(bookId); record.setReaderId(readerId); record.setStatus(0); recordMapper.insert(record); bookMapper.updateRemaining(bookId, -1); return record.getId(); }selectByIdForUpdate在事务里对图书行加悲观锁两个请求同时借同一本书时后一个请求等前一个提交后再查避免超借。毕设项目并发量不大用悲观锁解释起来比乐观锁容易。rollbackFor Exception.class确保运行时异常和检查异常都触发回滚如果不写默认只在运行时异常时回滚。更稳妥的防超卖写法是让 update 语句自己校验库存update book set remaining remaining - 1 where id #{id} and remaining 0影响行数为 0 就证明没抢到。这种写法把并发控制下推到数据库代码里少一次二次检查也更好解释。4.3 列表查询的分页和动态条件图书列表在毕业设计里通常是分页加关键字搜索。没引分页插件时自己用 LIMIT 足够select idselectPage resultTypecom.example.library.entity.Book select id, book_no, name, author, publisher, stock, remaining from book where if testkeyword ! null and keyword ! and (name like concat(%, #{keyword}, %) or author like concat(%, #{keyword}, %)) /if /where order by id desc limit #{offset}, #{pageSize} /selectoffset 由 Service 层根据页码算好(pageNum - 1) * pageSize。#{keyword}是预编译参数不会把单引号拼进 SQL从源头防注入。注意 MyBatis 里不能直接写where name like %#{keyword}%因为 #{} 会被解析成参数占位符前后的单引号不属于 SQL 语法需要运行时经常报错。这里用 concat 拼接是最常见的做法。如果项目引入了 PageHelper则不用手算 offset只要在 Service 里 startPage 后紧跟第一行查询即可但根据源码里的依赖来看这个项目默认没加LIMIT 方案足以应付。5. Maven 打包与线上部署的最后一公里项目在本地能跑不等于到答辩机器上能跑。部署的第一步不是点 IDEA 绿色三角而是确认命令行里能执行mvn -v然后通过 Maven 把项目打包成可执行 Jar。这里会涉及 Maven 依赖管理的两个关键点本地仓库和镜像配置。settings.xml 里的localRepository决定依赖存到哪个磁盘目录mirror决定去哪里下载依赖。国内网络环境下在 settings.xml 配一个阿里云公共仓库通常是最直接的加速方式mirror idaliyunmaven/id mirrorOfcentral/mirrorOf urlhttps://maven.aliyun.com/repository/public/url /mirror5.1 用 Maven 打包并指定运行参数打包命令和启动命令可以放在一起看mvn clean package -DskipTests java -jar target/library-0.0.1-SNAPSHOT.jar \ --spring.datasource.passwordyourpassword-DskipTests跳过测试编译和运行避免环境差异导致打包失败。--spring.datasource.passwordyourpassword是 Spring Boot 的配置覆盖语法优先级高于 application.yml临时换数据库密码时比改配置文件更安全。如果项目模块多需要在根目录执行 mvn而不是进了某个子模块再执行否则会提示找不到父级 POM。5.2 验证接口与三个高频启动坑服务起来后先看日志里有没有 Started LibraryApplication 字样再访问http://localhost:8080/api/book/list。返回 JSON 里有 code、msg、data说明 Controller 映射正常。如果页面 404先去 Controller 的 RequestMapping 确认前缀这个项目常见的是/api/book不是/book。启动时还有三个高频报错值得记一下一是数据源密码错误启动报 Access denied for user检查 application.yml 和命令行参数二是 MySQL 驱动类名写错报 ClassNotFoundException三是 MySQL 8 下报 Public Key Retrieval is not allowed在数据源 url 末尾加allowPublicKeyRetrievaltrue即可它只影响本机认证不影响运行期性能。本文还有配套的精品资源点击获取