第 5 章 [生产力] MyBatis 生态工具

第 5 章 [生产力] MyBatis 生态工具

摘要: 优秀的框架往往拥有一个繁荣的生态。MyBatis 也不例外,社区为其贡献了许多强大的工具来自动化常见的开发任务。本章我们将聚焦于两个最核心的生产力工具:首先,我们将学习如何使用 PageHelper 插件,用两行代码优雅地实现曾一度令人头疼的物理分页功能;接着,我们将掌握 MyBatis Generator 逆向工程,实现根据数据库表一键自动生成 POJO、Mapper 接口和 XML 文件,彻底告别重复的手动编码。


5.1. [分页] PageHelper - 优雅的分页助手

分页是 Web 开发中最常见的需求之一。如果没有工具,我们需要手动实现非常繁琐的逻辑。

5.1.1. 背景:手动分页的痛点

  • SQL 耦合: 我们需要在 SQL 语句中硬编码 LIMIT ?, ?,这意味着 Mapper 接口的每个分页方法都需要额外接收 startIndexpageSize 两个参数。
  • 功能割裂: 为了在页面上显示“共 X 页,共 Y 条”等信息,我们通常需要执行一条额外的 SELECT COUNT(*) 查询来获取总记录数。这两步操作在业务逻辑中是分开的,管理起来很麻烦。

PageHelper 的出现,完美地解决了这些问题。它以非侵入的方式,让我们对分页的处理变得极其简单。

5.1.2. PageHelper 快速上手

集成 PageHelper 只需两步。

1. 添加 Maven 依赖

首先,我们在 pom.xml 文件中添加 PageHelper 的依赖。

文件路径: pom.xml

1
2
3
4
5
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.3</version>
</dependency>

2. 配置 MyBatis 插件

PageHelper 的工作原理是作为 MyBatis 的一个 拦截器 (Interceptor),它会拦截我们即将执行的 SQL 语句,并自动地在末尾追加上物理分页的关键字(如 LIMIT)。我们需要在全局配置文件中注册这个拦截器。

文件路径: src/main/resources/mybatis-config.xml

危险: 根据 MyBatis DTD 规范,正确的顺序应该是如下的代码块,一定要严格按照,非常严格,否则很容易导致报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 1. settings 必须在前面 -->
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>

<!-- 2. plugins 必须在 environments 之前 -->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>

<!-- 3. environments 在 plugins 之后 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/bank_db"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>

<!-- 4. mappers 必须放在最后 -->
<mappers>
<mapper resource="mappers/AccountMapper.xml"/>
<mapper resource="mappers/CarMapper.xml"/>
<mapper resource="mappers/CustomerMapper.xml"/>
</mappers>
</configuration>

5.1.3. 实践与演示

PageHelper 最具魅力的一点是:我们无需修改任何已有的 Mapper 接口和 SQL 语句。我们可以对任何一个返回 List 的查询方法进行分页。

1. 准备一个查询全部的方法

我们在 CarMapper 中准备一个查询所有汽车信息的方法。

文件路径 (接口): src/main/java/com/example/bank/mapper/CarMapper.java (添加方法)

1
List<Car> selectAll();

文件路径 (XML): src/main/resources/mappers/CarMapper.xml (添加)

1
2
3
<select id="selectAll" resultType="com.example.bank.pojo.Car">
select id, car_num, brand, guide_price, produce_time, car_type from t_car
</select>

2. 编写测试

现在,我们来见证 PageHelper 的神奇之处。

核心用法:

  1. 在执行查询 之前,调用 PageHelper.startPage(pageNum, pageSize) 来“声明”接下来的一次查询需要分页。
  2. 正常执行你的查询方法。
  3. new PageInfo<>(查询结果) 来包装 List,从中获取详尽的分页信息。

文件路径: src/test/java/com/example/bank/test/PageHelperTest.java (新建)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.example.bank.test;

import com.example.bank.mapper.CarMapper;
import com.example.bank.pojo.Car;
import com.example.bank.utils.SqlSessionUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Test;
import java.util.List;

@Slf4j
public class PageHelperTest {

@Test
void testPageHelper() {
try (SqlSession sqlSession = SqlSessionUtil.openSession()) {
CarMapper carMapper = sqlSession.getMapper(CarMapper.class);

// 1. 开启分页 (查询第 2 页,每页 2 条数据)
int pageNum = 1;
int pageSize = 2;
PageHelper.startPage(pageNum, pageSize);

// 2. 正常执行查询
List<Car> cars = carMapper.selectAll();

// 3. 使用 PageInfo 包装查询结果,获取分页详细信息
// PageInfo 的构造方法中,第二个参数 5 表示导航分页的页码数量
// << 1 2 3 4 5 > >
PageInfo<Car> pageInfo = new PageInfo<>(cars, 5);

log.info("分页信息: {}", pageInfo);

}
}
}// 输出:
// 日志将显示 PageHelper 自动执行了 SELECT COUNT(*) 查询,并在原查询后追加了 LIMIT
// INFO ... - 分页信息: PageInfo{pageNum = 2, pageSize = 2, size = 2, startRow = 3,
// endRow = 4, total = 4, pages = 2, ...}

前瞻提示: 在 Spring Boot 项目中集成 PageHelper 更加简单,通常只需引入一个 pagehelper-spring-boot-starter 依赖,连 XML 中的插件配置都可以省略,真正做到开箱即用。


5.2. [提效] MyBatis Generator - 告别重复劳动 (终极讲解重构版)

对于一个拥有几十甚至上百张表的项目,手动为每张表创建 POJO、Mapper 接口和 XML 文件,是一项极其枯燥、耗时且容易出错的工作。MyBatis Generator (MBG) 就是为了解决这个问题而生的自动化工具。

5.2.1. Generator 配置详解

配置 MBG 分为两步:在 pom.xml 中引入插件,并编写一个 generatorConfig.xml 配置文件。

1. 配置 Maven 插件

首先,我们需要在项目的 pom.xml 中引入 mybatis-generator-maven-plugin 插件,并为其提供数据库驱动。

文件路径: pom.xml (在 <build> 标签内添加)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.2</version>
<dependencies>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.2.0</version>
</dependency>
</dependencies>
<configuration>
<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
</build>

2. 详解 generatorConfig.xml

这个文件是 MBG 的“行动指南”,也是我们自定义生成规则的核心。下面,我们将逐一拆解它的关键配置项。

文件路径: src/main/resources/generatorConfig.xml (新建)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<!--
这个文件是 MBG 的“行动指南”,也是我们自定义生成规则的核心。
下面,我们将逐一拆解它的关键配置项。
-->
<generatorConfiguration>
<!--
<context> 标签
这是最核心的根标签,包裹了所有的生成配置。
- id: 为这个配置上下文指定一个唯一的 ID。
- targetRuntime: **必须** 指定为 `MyBatis3`,以生成适用于现代 MyBatis 版本的代码。
-->
<context id="MySqlContext" targetRuntime="MyBatis3">

<!--
<jdbcConnection> 标签
用于配置数据库连接信息,MBG 需要连接数据库来读取表的元数据(字段、类型等)。
- driverClass: 数据库驱动的全限定名。
- connectionURL: 数据库的 JDBC 连接地址。
- userId 和 password: 数据库的用户名和密码。
-->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/bank_db"
userId="root"
password="root"/>

<!--
<javaModelGenerator> 标签
用于配置 POJO 实体类 的生成规则。
- targetPackage: 指定生成的 POJO 类要放在哪个包下,例如 `com.example.bank.pojo.gen`。
- targetProject: 指定生成的代码要输出到哪个源码根目录,通常是 `src/main/java`。
-->
<javaModelGenerator targetPackage="com.example.bank.pojo.gen"
targetProject="src/main/java"/>

<!--
<sqlMapGenerator> 标签
用于配置 Mapper XML 文件 的生成规则。
- targetPackage: 指定生成的 XML 文件要放在哪个包路径下,例如 `com.example.bank.mapper.gen`。
- targetProject: 指定生成的资源文件要输出到哪个资源根目录,通常是 `src/main/resources`。
-->
<sqlMapGenerator targetPackage="com.example.bank.mapper.gen"
targetProject="src/main/resources"/>

<!--
<javaClientGenerator> 标签
用于配置 Mapper 接口 的生成规则。
- type: **必须** 指定为 `XMLMAPPER`,表示我们要为 XML 文件生成对应的 Java 接口。
- targetPackage: 指定生成的 Mapper 接口要放在哪个包下,例如 `com.example.bank.mapper.gen`。
- targetProject: 指定生成的代码要输出到哪个源码根目录,通常是 `src/main/java`。
-->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.example.bank.mapper.gen"
targetProject="src/main/java"/>

<!--
<table> 标签
用于指定要为哪张数据库表生成代码,可以配置多个 `<table>` 标签。
- tableName: **必须**,指定数据库中的表名。
- domainObjectName: **必须**,指定生成的 POJO 类的类名。
-->
<table tableName="t_account" domainObjectName="AccountGen"/>
<table tableName="t_customer" domainObjectName="CustomerGen"/>

</context>
</generatorConfiguration>

请注意,targetProjecttargetPackage 会组合在一起决定最终的输出路径。例如 targetProject="src/main/java"targetPackage="com.example.bank.pojo.gen" 会将文件生成在 src/main/java/com/example/bank/pojo/gen/ 目录下。

5.2.2. 执行生成与验证

1. 执行生成

在 IDEA 的 Maven 面板中,找到 mybatis-generator 插件,并双击 generate 目标来执行。

执行成功后,您的项目目录中会自动生成如下文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── bank
│ │ ├── mapper
│ │ │ └── gen
│ │ │ ├── AccountGenMapper.java
│ │ │ └── CustomerGenMapper.java
│ │ └── pojo
│ │ └── gen
│ │ ├── AccountGen.java
│ │ ├── AccountGenExample.java
│ │ ├── CustomerGen.java
│ │ └── CustomerGenExample.java
│ └── resources
│ └── com
│ └── example
│ └── bank
│ └── mapper
│ └── gen
│ ├── AccountGenMapper.xml
│ └── CustomerGenMapper.xml

2. 验证生成的代码

MBG 生成的代码非常强大,它不仅包含了基础的 CRUD,还有一个 Example 类,可以让我们以面向对象的方式构建复杂的 WHERE 子句(这种方式被称为 QBC - Query By Criteria)。

文件路径: src/test/java/com/example/bank/test/GeneratorTest.java (新建)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.example.bank.test;

import com.example.bank.mapper.gen.AccountGenMapper;
import com.example.bank.pojo.gen.AccountGen;
import com.example.bank.pojo.gen.AccountGenExample;
import com.example.bank.utils.SqlSessionUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;

@Slf4j
public class GeneratorTest {
@Test
void testGeneratorCode() {
try (SqlSession sqlSession = SqlSessionUtil.openSession()) {
// MBG 生成的 Mapper 接口可以直接使用
AccountGenMapper mapper = sqlSession.getMapper(AccountGenMapper.class);

// 使用 Example 类构建查询条件:查询余额大于 20000 的账户
AccountGenExample example = new AccountGenExample();
example.createCriteria().andBalanceGreaterThan(20000.00);

// 执行查询,无需手写 SQL
List<AccountGen> accounts = mapper.selectByExample(example);

assertThat(accounts).hasSize(1);
assertThat(accounts.get(0).getActno()).isEqualTo("act-001");
log.info("MBG 生成的代码查询成功: {}", accounts);
}
}
}
// 输出:
// INFO ... - MBG 生成的代码查询成功: [AccountGen{id = 1, actno ='act-001', balance = 50000.0, customerId = 1}]

可以看到,通过详细配置,MBG 成为了我们手中的一把利器。它为我们生成了功能完备、类型安全的数据访问层代码,使我们能从重复的劳动中解放出来,更专注于业务逻辑的实现。