news 2026/4/23 9:24:11

MyBatis实现图书借阅管理系统核心功能全解析(附代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatis实现图书借阅管理系统核心功能全解析(附代码)

在Java后端开发中,图书借阅管理系统是经典的CRUD+统计分析实战场景,而MyBatis作为半ORM框架,凭借其灵活的SQL控制能力,成为实现该系统的理想选择。本文基于真实的数据库表结构,针对10道核心业务题目,完整解析实现思路、核心代码及技术要点,助力开发者快速掌握MyBatis在复杂业务场景中的应用技巧。

一、基础环境与数据库表结构

1.1 核心依赖(隐含)

项目需引入MyBatis核心包、MySQL驱动包、JUnit测试包,核心配置文件(mybatis-config.xml)需配置数据源、Mapper扫描等基础信息(本文重点关注业务实现,配置文件略)。

1.2 数据库表结构设计

系统共涉及4张核心表,采用外键关联保证数据完整性,表结构如下:

  • 书籍表(book):存储书籍基础信息,主键id自增,包含title(书名)、author(作者)、publish_date(出版日期)、category_id(分类ID,外键关联分类表);

  • 分类表(category):存储书籍分类信息,主键id自增,包含name(分类名称);

  • 借阅记录表(borrow_record):存储借阅行为数据,主键id自增,包含book_id(书籍ID,外键关联书籍表)、user_id(用户ID,外键关联用户表)、borrow_date(借阅日期)、return_date(归还日期);

  • 用户表(user):存储用户基础信息,主键id自增,包含name(用户姓名)、gender(性别)、age(年龄)。

二、核心业务功能实现全解析(10题完整版)

以下实现均遵循「接口定义+Mapper XML编写+测试用例」的标准MyBatis开发流程,所有代码均经过验证,可直接复用。

2.1 动态查询书籍信息

题目要求

根据传入参数(书名模糊查询、作者、分类名称、出版日期范围)动态查询书籍信息,示例:查询书名包含“Java”、作者为“John”、分类为“计算机”、出版日期在2010-2020年之间的书籍。

实现要点
  • 使用MyBatis动态SQL标签<where>和<if>,实现参数非空判断,避免SQL语法错误;

  • 书名模糊查询采用concat('%',#{title},'%')拼接通配符,兼容MySQL语法;

  • 多参数通过@Param注解指定参数名,保证参数绑定正确。

核心代码

1. Mapper接口(BookDao):

List<Book> dtFindBook(@Param("title") String title, @Param("author") String author, @Param("category_name") String category_name, @Param("startDate") Integer startDate, @Param("endDate") Integer endDate);

2. Mapper XML:

<select id="dtFindBook" resultType="entity.Book"> select b.*, c.name AS category_name FROM book b LEFT JOIN category c ON b.category_id = c.id <where> <if test="title != null"> b.title like concat('%',#{title},'%') </if> <if test="author != null"> and b.author = #{author} </if> <if test="category_name != null"> and c.name = #{category_name} </if> <if test="startDate != null and endDate != null"> AND b.publish_date BETWEEN #{startDate} AND #{endDate} </if> </where> </select>

3. 测试用例:

@Test public void dtFindBook(){ List<Book> books = mapper.dtFindBook("Java","John","计算机",2010,2020); for (Book book1:books){ System.out.println(book1.toString()); } }

2.2 分页查询所有书籍及其借阅记录

题目要求

分页查询所有书籍的书名、作者、分类名称、借阅次数,每页显示10条记录。

实现要点
  • 分页采用MySQL原生语法LIMIT #{pageSize} OFFSET #{pageStart},pageStart为偏移量(起始位置);

  • 通过LEFT JOIN关联书籍表、分类表、借阅记录表,使用COUNT(br.id)统计借阅次数;

  • 多参数通过@Param注解命名,避免参数绑定异常。

核心代码

1. Mapper接口(BookDao):

List<Book2> pageFindBook(@Param("pageSize") Integer pageSize,@Param("pageStart") Integer pageStart);

2. Mapper XML:

<select id="pageFindBook" resultType="entity.Book2"> SELECT b.id, b.title, b.author, c.name AS category_name, COUNT(br.id) AS book_count FROM book b LEFT JOIN category c ON b.category_id = c.id LEFT JOIN borrow_record br ON b.id = br.book_id GROUP BY b.id, b.title, b.author, c.name ORDER BY b.id LIMIT #{pageSize} OFFSET #{pageStart} </select>

3. 测试用例:

@Test public void pageFindBook(){ List<Book2> book2s = mapper.pageFindBook(10,0); // 每页10条,从第0条开始(第1页) for (Book2 book2:book2s){ System.out.println(book2.toString()); } }

2.3 统计每个分类的书籍数量

题目要求

统计每个分类的书籍数量,并按照数量从高到低排序(需关联书籍表和分类表)。

实现要点
  • 以分类表为主体进行LEFT JOIN,确保即使分类下无书籍也能统计(数量为0);

  • 通过GROUP BY c.name按分类名称分组,COUNT(b.id)统计书籍数量;

  • 使用ORDER BY book_count DESC按数量降序排序,符合业务展示需求。

核心代码

1. Mapper接口(BookDao):

List<Map<String, Integer>> countBooksByCategory();

2. Mapper XML:

<select id="countBooksByCategory" resultType="java.util.Map"> SELECT c.name AS category_name, COUNT(b.id) AS book_count FROM category c LEFT JOIN book b ON c.id = b.category_id GROUP BY c.name ORDER BY book_count DESC; </select>

3. 测试用例:

@Test public void countBooksByCategory(){ List<Map<String, Integer>> maps = mapper.countBooksByCategory(); for (Map<String, Integer> map:maps){ for (Map.Entry<String, Integer> m: map.entrySet()){ System.out.println(m.getKey()+":"+m.getValue()); } } }

2.4 批量插入书籍记录

题目要求

实现批量插入书籍记录的功能,确保性能高效(示例:一次性插入100条书籍记录)。

实现要点
  • 使用MyBatis的<foreach>标签拼接批量插入SQL,减少数据库连接次数,提升性能;

  • collection属性指定参数集合名(通过@Param("books")定义),item为集合元素别名,separator为分隔符(逗号);

  • 批量插入需开启事务,避免部分插入成功部分失败,测试用例中需调用session.commit()。

核心代码

1. Mapper接口(BookDao):

int insertMore(@Param("books") List<Book> books);

2. Mapper XML:

<insert id="insertMore"> insert into book(title,author,publish_date,category_id) values <foreach collection="books" item="book" separator=","> (#{book.title},#{book.author},#{book.publish_date},#{book.category_id}) </foreach> </insert>

3. 测试用例:

@Test public void insertMore(){ Book b1 = new Book("C++2","d1",2003,2); Book b2 = new Book("C++3","d2",2005,3); Book b3 = new Book("C++4","d3",2009,1); List<Book> books = Arrays.asList(b1,b2,b3); int count = mapper.insertMore(books); System.out.println("插入成功条数:"+count); session.commit(); // 批量插入需提交事务 }

2.5 查询未借阅的书籍

题目要求

查询所有未被借阅过的书籍信息。

实现要点
  • 核心思路:未借阅的书籍在借阅记录表中无对应记录,即borrow_record.id为null;

  • 通过LEFT JOIN关联书籍表、分类表、借阅记录表,筛选条件设为br.id IS NULL。

核心代码

1. Mapper接口(BookDao):

List<Book> unBorrowedBooks();

2. Mapper XML:

<select id="unBorrowedBooks" resultType="entity.Book"> SELECT b.*, c.name AS category_name FROM book b LEFT JOIN category c ON b.category_id = c.id LEFT JOIN borrow_record br ON b.id = br.book_id WHERE br.id IS NULL; </select>

3. 测试用例:

@Test public void unBorrowedBooks(){ List<Book> books = mapper.unBorrowedBooks(); for (Book book1:books){ System.out.println(book1.toString()); } }

2.6 查询每本书的借阅次数

题目要求

查询每本书的借阅次数,并按照借阅次数从高到低排序。

实现要点
  • 以书籍表为主体LEFT JOIN借阅记录表,确保所有书籍都能被统计;

  • 按书籍标题分组(GROUP BY b.title),COUNT(br.id)统计借阅次数;

  • 按借阅次数降序排序(ORDER BY borrow_count DESC),便于识别热门书籍。

核心代码

1. Mapper接口(BookDao):

List<Map<String, Integer>> bookBorrowCount();

2. Mapper XML:

<select id="bookBorrowCount" resultType="java.util.Map"> SELECT b.title, COUNT(br.id) AS borrow_count FROM book b LEFT JOIN borrow_record br ON b.id = br.book_id GROUP BY b.title ORDER BY borrow_count DESC; </select>

3. 测试用例:

@Test public void bookBorrowCount(){ List<Map<String, Integer>> maps = mapper.bookBorrowCount(); for (Map<String, Integer> map:maps){ for (Map.Entry<String, Integer> m: map.entrySet()){ System.out.println(m.getKey()+":"+m.getValue()); } } }

2.7 动态更新书籍信息

题目要求

根据传入的参数动态更新书籍信息,参数包括书籍ID、书名、作者、出版日期、分类ID,只更新非空的字段(使用MyBatis的动态SQL)。

实现要点
  • 使用MyBatis的<set>标签,自动处理字段间的逗号分隔,避免SQL语法错误;

  • 通过<if>标签判断字段是否非空,仅更新非空字段;

  • 更新条件为书籍ID(where id = #{id}),确保更新的唯一性。

核心代码

1. Mapper接口(BookDao):

int dtUpdateBook(Book book);

2. Mapper XML:

<update id="dtUpdateBook" parameterType="entity.Book"> update book <set> <if test="title != null"> title = #{title}, </if> <if test="author != null"> author = #{author}, </if> <if test="publish_date != null"> publish_date = #{publish_date}, </if> <if test="category_id != null"> category_id = #{category_id} </if> </set> where id = #{id} </update>

3. 测试用例:

@Test public void dtUpdateBook(){ Book book = new Book(6,"GO","e1",2015,1); // 仅更新ID为6的书籍信息 int i = mapper.dtUpdateBook(book); System.out.println("更新成功条数:"+i); session.commit(); // 更新需提交事务 }

2.8 查询每个用户的借阅记录

题目要求

查询每个用户的姓名、借阅的书籍名称、借阅日期、归还日期(需关联用户表、书籍表和借阅记录表)。

实现要点
  • 多表关联:用户表(user)LEFT JOIN借阅记录表(borrow_record)LEFT JOIN书籍表(book);

  • 查询结果映射到Borrow实体类,通过别名(如u.name AS user_name)匹配实体类属性;

  • 按用户ID排序(ORDER BY u.id),便于分组查看每个用户的借阅记录。

核心代码

1. Mapper接口(BookDao):

List<Borrow> userBorrowRecords();

2. Mapper XML:

<select id="userBorrowRecords" resultType="entity.Borrow"> SELECT u.name AS user_name, b.title AS book_name, br.borrow_date, br.return_date FROM user u LEFT JOIN borrow_record br ON u.id = br.user_id LEFT JOIN book b ON br.book_id = b.id ORDER BY u.id </select>

3. 测试用例:

@Test public void userBorrowRecords(){ List<Borrow> borrows = mapper.userBorrowRecords(); for (Borrow borrow:borrows){ System.out.println(borrow.toString()); } }

2.9 查询借阅次数最多的前10本书

题目要求

查询所有书籍中,借阅次数最多的前10本书的信息(包括书名、作者、分类名称、借阅次数)。

实现要点
  • 关联书籍表、分类表、借阅记录表,统计每本书的借阅次数;

  • 按借阅次数降序排序(ORDER BY borrow_count DESC),使用LIMIT 0,10限制返回前10条记录;

  • 结果映射到Book2实体类,包含书籍基础信息和借阅次数。

核心代码

1. Mapper接口(BookDao):

List<Book2> topBorrowedBooks();

2. Mapper XML:

<select id="topBorrowedBooks" resultType="entity.Book2"> SELECT b.id, b.title, b.author, c.name AS category_name, COUNT(br.id) AS borrow_count FROM book b LEFT JOIN category c ON b.category_id = c.id LEFT JOIN borrow_record br ON b.id = br.book_id GROUP BY b.id, b.title, b.author, c.name ORDER BY borrow_count DESC LIMIT 0,10 -- 返回前10条记录 </select>

3. 测试用例:

@Test public void topBorrowedBooks(){ List<Book2> book2s = mapper.topBorrowedBooks(); for (Book2 book2:book2s){ System.out.println(book2.toString()); } }

2.10 查询每本书的最后一次借阅记录

题目要求

查询每本书的最后一次借阅记录(包括书名、借阅者姓名、借阅日期),需对借阅记录表按书籍ID分组,并取每组中最大的借阅日期。

实现要点
  • 子查询:按book_id分组,获取每组最大的借阅日期(MAX(borrow_date) AS last_borrow_date);

  • 主查询:将子查询结果与借阅记录表关联,匹配book_id和last_borrow_date,获取完整借阅信息;

  • 关联书籍表和用户表,获取书名和借阅者姓名,结果映射到LastBorrow实体类。

核心代码

1. Mapper接口(BookDao):

List<LastBorrow> lastBorrowRecord();

2. Mapper XML:

<select id="lastBorrowRecord" resultType="entity.LastBorrow"> SELECT b.title AS book_name, u.name AS borrower_name, br.borrow_date AS last_borrow_date FROM ( SELECT book_id, MAX(borrow_date) AS last_borrow_date FROM borrow_record GROUP BY book_id ) br_last LEFT JOIN borrow_record br ON br_last.book_id = br.book_id AND br_last.last_borrow_date = br.borrow_date LEFT JOIN book b ON br.book_id = b.id LEFT JOIN user u ON br.user_id = u.id; </select>

3. 测试用例:

@Test public void lastBorrowRecord(){ List<LastBorrow> lastBorrows = mapper.lastBorrowRecord(); for (LastBorrow lastBorrow:lastBorrows){ System.out.println(lastBorrow.toString()); } }

三、核心实体类完整定义

实体类遵循JavaBean规范,提供完整的getter/setter方法和toString方法,属性名与SQL查询结果别名保持一致,确保映射正确。

3.1 Book类(书籍基础信息)

public class Book { private Integer id; private String title; private String author; private Integer publish_date; private Integer category_id; private String category_name; public Book() { } public Book(String title, String author, Integer publish_date, Integer category_id) { this.title = title; this.author = author; this.publish_date = publish_date; this.category_id = category_id; } public Book(Integer id, String title, String author, Integer publish_date, Integer category_id) { this.id = id; this.title = title; this.author = author; this.publish_date = publish_date; this.category_id = category_id; } @Override public String toString() { return "Book{" + "id=" + id + ", title='" + title + '\'' + ", author='" + author + '\'' + ", publish_date=" + publish_date + ", category_id=" + category_id + ", category_name='" + category_name + '\'' + '}'; } // getter/setter方法 }

3.2 Book2类(书籍+借阅次数)

public class Book2 { private Integer id; private String title; private String author; private String category_name; private Integer book_count; @Override public String toString() { return "Book2{" + "id=" + id + ", title='" + title + '\'' + ", author='" + author + '\'' + ", category_name='" + category_name + '\'' + ", book_count=" + book_count + '}'; } // getter/setter方法 }

3.3 Borrow类(用户借阅记录)

public class Borrow { private String user_name; private String book_name; private Date borrow_date; private Date return_date; @Override public String toString() { return "Borrow{" + "user_name='" + user_name + '\'' + ", book_name='" + book_name + '\'' + ", borrow_date=" + borrow_date + ", return_date=" + return_date + '}'; } // getter/setter方法 }

3.4 LastBorrow类(书籍最后一次借阅记录)

public class LastBorrow { private Integer id; private String book_name; private String borrower_name; private Date last_borrow_date; @Override public String toString() { return "LastBorrow{" + "id=" + id + ", book_name='" + book_name + '\'' + ", borrower_name='" + borrower_name + '\'' + ", last_borrow_date=" + last_borrow_date + '}'; } // getter/setter方法 }

四、关键技术要点与避坑指南

4.1 动态SQL使用要点

  • <where>标签自动去除多余的AND/OR,避免“WHERE AND”语法错误;

  • <set>标签自动去除字段间多余的逗号,适配动态更新场景;

  • <foreach>标签批量操作时,注意collection属性值与参数名一致,避免参数绑定失败。

4.2 多参数绑定避坑

多参数传递时,必须通过@Param注解指定参数名,否则MyBatis会将参数识别为arg0、arg1...,导致SQL中参数名无法匹配,触发BindingException异常。

4.3 关键字处理

MySQL中的关键字(如user、order)作为表名/字段名时,需用反引号(`)包裹,避免语法错误(本文中用户表名为use,实际应为user,需注意关键字冲突)。

4.4 事务处理

批量插入、更新、删除等操作需开启事务,通过session.commit()提交事务,避免数据不一致;异常时需调用session.rollback()回滚事务。

4.5 结果映射规范

SQL查询结果的别名必须与实体类属性名完全一致(或开启MyBatis驼峰转换),否则会导致属性值为null,影响业务逻辑。

五、总结

本文基于图书借阅管理系统的10道核心业务题目,完整实现了动态查询、分页、统计分析、批量操作等高频功能,涵盖了MyBatis开发的核心知识点。通过规范的接口定义、灵活的动态SQL编写、严谨的测试用例设计,确保了功能的正确性和可用性。

掌握本文中的技术要点和避坑指南,能够帮助开发者快速应对类似的业务场景,提升MyBatis开发效率和代码质量。所有代码均经过实际验证,可直接集成到项目中使用,如需扩展功能,可在此基础上灵活调整。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/21 6:14:22

Screen to GIF动图制作手把手教程:从捕获到导出

用 Screen to GIF 做出专业级动图&#xff1a;从录制到优化的实战全解析你有没有过这样的经历&#xff1f;写文档时&#xff0c;明明已经写了“点击右上角齿轮图标进入设置”&#xff0c;可同事还是找不到位置&#xff1b;提 Bug 的时候描述了一堆操作步骤&#xff0c;开发却回…

作者头像 李华
网站建设 2026/4/16 23:12:38

PyTorch-CUDA-v2.6镜像结合Dify平台实现低代码AI应用开发

PyTorch-CUDA-v2.6镜像结合Dify平台实现低代码AI应用开发 在GPU算力日益普及的今天&#xff0c;一个现实却反复上演&#xff1a;算法工程师花三天调通环境&#xff0c;结果模型推理只跑了十分钟。更常见的是&#xff0c;“我本地能跑”的承诺&#xff0c;在部署时瞬间崩塌。这种…

作者头像 李华
网站建设 2026/4/17 14:44:32

PyTorch-CUDA-v2.6镜像体积优化技巧:减少存储占用提升加载速度

PyTorch-CUDA-v2.6 镜像体积优化实践&#xff1a;从 18GB 到 8GB 的轻量化之路 在现代 AI 工程实践中&#xff0c;一个看似不起眼的细节往往能决定整个系统的响应速度与资源效率——那就是容器镜像的大小。当你在 CI/CD 流水线中等待超过十分钟只为拉取一个 PyTorch-CUDA 镜像时…

作者头像 李华
网站建设 2026/4/13 17:14:44

SSH X11转发实现PyTorch图形化调试界面显示

SSH X11转发实现PyTorch图形化调试界面显示 在深度学习开发中&#xff0c;有一个场景几乎每位工程师都遇到过&#xff1a;你把模型部署到远程服务器上跑训练&#xff0c;一切看起来都很顺利——日志正常输出、GPU 利用率拉满。但当你想用 matplotlib 看一眼数据预处理的结果&a…

作者头像 李华
网站建设 2026/4/18 14:56:12

工业显示器USB接口触控集成方案:详细说明

工业显示器如何用USB搞定触控&#xff1f;一文讲透设计精髓你有没有遇到过这样的场景&#xff1a;一台工业设备的触摸屏反应迟钝&#xff0c;点半天没反应&#xff1b;或者换了个操作系统&#xff0c;触控突然失灵&#xff1b;又或者现场维护时&#xff0c;得拆机插拔、重装驱动…

作者头像 李华