Mybatis三剑客

MyBatis 这种半封装的ORM框架相较全封装,最大的优点就是 SQL优化方便, 但也有不少痛点,如 简单的 CRUD 逻辑都需要自行处理,*.xml 不能和 *.java 文件友好契合等。

工欲善其事,必先利其器。这里介绍几个工具,可以有效的解决 Mybatis 的痛点,大大的提高工作效率。

涉及 说明
Mybatis Generator 代码生成器。
Mybatis Plugin IDE插件,解决 *.xml 不能和 *.java 文件完美契合等。
Mybatis PageHelper 分页插件。

Mybatis Generator

Mybatis Generator 是 Mybatis 提供的代码生成器,可以生成DAO层所需的*.java , *.xml实体类 ,包括可以简单的CRUD相关所需。

注意 : 默认情况下,反复运行 *.java 文件会被覆盖,而 *.xml 会合并。

1
2
3
4
5
6
7
8
<!-- generator -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>

generatorConfig.xml

踩坑记

  • table.schema : 使用oracle时,最好使用 schema 指定用户名,因为当实例下有多个用户拥有相同的表时,若对表进行变更,会无法及时更新。
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
<?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">

<!-- todo 字段注释 -->
<!-- 注意相关目录一定要存在,且分模块时要加模块目录名 -->

<generatorConfiguration>
<context id="MysqlContext" targetRuntime="MyBatis3" defaultModelType="flat">
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>

<!-- optional,旨在创建class时,去除多余警告注释等 -->
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
</commentGenerator>

<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/framework-x"
userId="root"
password="root">
</jdbcConnection>

<!-- 生成的pojo所在包 -->
<javaModelGenerator targetPackage="tk.gushizone.system.common.pojo" targetProject="system-common/src/main/java"/>

<!-- 生成的mapper所在目录 -->
<sqlMapGenerator targetPackage="tk.gushizone.system.common.dao.sqlmap" targetProject="system-common/src/main/java"/>
<!-- <sqlMapGenerator targetPackage="mapper" targetProject="web/src/main/resources"/> -->

<!-- 配置mapper对应的java映射 -->
<javaClientGenerator targetPackage="tk.gushizone.system.common.dao" targetProject="system-common/src/main/java"
type="XMLMAPPER" />

<!-- 作用表 (关闭所有示例) -->
<table tableName="sys_user" enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"/>

</context>
</generatorConfiguration>

启动方式

generator 有很多启动方式,个人认为 main方法 启动,最为友好,以下为示例。

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
package tk.gushizone.system.common.util;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
* mybatis generator
* @author gushizone@gmail.com
* @createTime 2019-05-06 15:33
*/
public class GeneratorDisplay {

private static final String ACTIVE_CONFIG = "generatorConfig.xml";

public static void main(String[] args) throws Exception {
ClassLoader classLoader = GeneratorDisplay.class.getClassLoader();
GeneratorDisplay.generator(classLoader.getResource(ACTIVE_CONFIG).getFile());
}

public static void generator(String configMode) throws Exception {
List<String> warnings = new ArrayList<>();
boolean overwrite = true;
File configFile = new File(configMode);
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}

}

Mybatis Plugin

相关IDE插件有很多,以 IDEA 为例,我使用的是 Free Mybatis Plugin 。类似的 Mybatis PluginMybatis Plus 功能更强大,但是收费。

主要功能

  • 关联 *.java*.xml ,并提供相关验证。
  • 自动补全和代码生成等。
  • Free Mybatis Plugin 新版已支持 generator!

Mybatis PageHelper

Mybatis PageHelper 是一款非常好用的 Mybatis 分页插件。支持各种数据库,使用起来也十分方便。

1
2
3
4
5
6
<!-- pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
1
2
3
4
5
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql

分页方式

这里简单的介绍一下利用 PageHelper 实现分页的方式。

Mybatis PageHelper 提供了很多种实现方式,这里只列举常用的方式。

涉及 说明
PageHelper.startPage 静态方法 推荐开发使用,耦合度和代码侵入性小。
ISelect接口 方式 推荐框架使用,代码侵入性小,且安全性高。

PageHelper.startPage 静态方法

这里介绍一种常用的方式: PageHelper.startPage + PageInfo

分页时,sql 会被拦截。分页后,实际返回是 Page<E> 类型的对象,PageArrayList 的子类 , 如果想取出分页信息,需要强制转换为 Page<E> ,或使用 PageInfo

1
2
3
4
5
6
// 获取第1页,10条内容,默认查询总数count
PageHelper.startPage(1, 10);
//紧跟着的第一个select方法会被分页
List<Country> list = countryMapper.selectAll();
//用PageInfo对结果进行包装
PageInfo page = new PageInfo(list);

ISelect接口 方式

JDK8后,配合 lambda表达式 更适合架构设计。 ISelect接口方式 在 JDK8 以前使用并不友好,这里不加赘述。

可以将此段代码封装到架构底层,再用 lambda 配合 ISelect接口 ,可以做到分页代码在上层业务代码中达到无侵入。

1
2
3
4
5
// 返回Page对象
Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(()-> countryMapper.selectGroupBy());

// 返回PageInfo对象
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> countryMapper.selectGroupBy());

排序

1
2
3
4
5
6
7
8
9
10
String order = StringUtils.EMPTY;
if (StringUtils.isNotEmpty(sortField)){
order = sortField + " " + sortOrder;
}

// 开始分页
PageHelper.startPage(pageNum, pageSize)
.orderBy(order);

List<SysUser001Dto> list = mapper.selectByList(user);

注意项

PageHelper.startPage 方法重要提示

只有紧跟在 PageHelper.startPage 方法后的第一个Mybatis的查询(Select)方法会被分页。

线程安全性 :只要 PageHelper.startPage 静态方法后跟着一个select 方法就会线程安全,因为分页参数和线程是绑定的 。反之,生产的分页参数会一直存于该线程中,没被消费,当这个该线程被再次调用时,可能会产生了莫名其妙的分页。所以一定要避免中间可能会抛异常的操作。

1
2
3
4
5
6
7
8
9
// 开始分页
PageHelper.startPage(pageNum, pageSize);

// 错误的示范:中间很可能抛异常,然后造成下次异常分页
if (StringUtils.isNotEmpty(sortField)){
PageHelper.orderBy(sortField + " " + sortOrder);
}

List<SysUser001Dto> list = mapper.selectByList(user);

分页插件不支持嵌套结果映射

由于嵌套结果方式会导致结果集被折叠,因此分页查询的结果在折叠后总数会减少,所以无法保证分页结果数量正确。即 不支持Mybatis一对多的正常分页

解决方案

  1. 主子表分开查询,(若需要)先条件过滤子表,再分页主表,再获取子表信息。
  2. 主子表一起查询,逻辑分页。

分页插件不支持带有 for update 语句的分页

对于带有for update的sql,会抛出运行时异常,对于这样的sql建议手动分页,毕竟这样的sql需要重视。

请不要配置多个分页插件

请不要在系统中配置多个分页插件(使用Spring时,mybatis-config.xmlSpring<bean>配置方式,请选择其中一种,不要同时配置多个分页插件)!