简介
Mybatis Generator (MBG) 是 Mybatis 官方提供的代码生成器,通过它可以在项目中自动生成简单的 CRUD 方法,甚至“无所不能”的高级条件查询(MyBatis3DynamicSql ),让我们避免了进行数据库交互时需要手动创建对象和配置 Mybatis 映射等基础工作。
另外,MBG 有很好地扩展性,它提供了大量的接口和插件用来给我们自定义生成的代码应该是什么样子,例如我们可以自定义注释、代码格式化、添加 toString 方法等。本文将讲解如何使用这些接口。
本文内容大致如下,由于篇幅较长,可选择阅读:
- 如何使用 MBG 生成代码;
- 详解 MBG 的配置,将配置使用自定义注释生成器、实体类中添加 toString/equals/hashCode方法等。
- MyBatis3DynamicSql 风格(无 XML) API 的使用。
通过本文的学习,你将能够通过简单改造 MBG 来生成自己想要的代码,另外,我们也将认识强大的 MyBatis3DynamicSql 风格(它提供的条件类使用 Lambda 解耦,全注解,支持单表查询、多表查询、分页、排序、分组等等)。
关于 MBG 生成代码的风格
MBG 支持生成不同风格、不同语言的代码,例如,MBG 能够生成 Java 或 Kotlin 代码。另外,MBG 支持生成旧版的 MyBatis3 风格(我们常用的 xml 配置属于 MyBatis3 风格,官方认为这种风格已经过时),也支持新版的 MyBatis3DynamicSql 的风格(MyBatis3DynamicSql 风格为官方推荐)。几种风格的对比如下:
由于 MyBatis3 风格生成的 Example 类存在的问题,实际项目中建议使用 MyBatis3Simple 风格或官方推荐的 MyBatis3DynamicSql 风格。
关于 MBG 文件覆盖的问题
当我们在迭代开发环境中使用 MBG,需要注意文件覆盖的问题,默认情况下,文件覆盖规则如下:
如果 XML 已经存在,MBG 会采用文件合并的方式。
它不会修改你自定义的节点,但是会更新原来生成的 CRUD 节点(如果表发生变化)。文件合并有个前提,就是原来生成的 CRUD 节点必须包含 @mbg.generated 的默认注释。否则,当再次运行 MBG 时,它将无法识别哪些是它生成过的节点,于是会出现下图的情况,即 CRUD 节点被重复插入。
![https://img2020.cnblogs.com/blog/1731892/202005/1731892-20200502235440091-1666766703.png)
- 如果 Java 或 Kotlin 文件已经存在,MBG 可以覆盖现有文件或使用其他唯一名称保存新生成的文件,这取决于你如何配置
<overwrite>false</overwrite>
。
那么,下面开始详细介绍如何使用 MBG。
项目环境的说明
工程环境
JDK:1.8.0_231(要求 JDK8 及以上)
maven:3.6.1
IDE:Spring Tool Suites4 for Eclipse 4.12
mysql:5.7.28
数据库脚本
具体的 sql 脚本也提供好了(脚本路径)。
maven配置
pom.xml配置
MBG 支持使用Java 代码、maven 插件、Eclipse 插件等方式运行,本文使用 maven 插件方式,所以需要在 build/plugins 节点引入 MBG 插件,并加入其它依赖项,例如 mybatis、mysql 驱动等。另外,因为本文也需要用到 Java 程序来运行 MBG(当使用自定义类时),所以把插件的依赖项从 build/plugins/plugin 节点中单独取出来。
<dependencies>
<!-- mybatis-generator -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.0</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!-- dynamic-sql 用于测试MyBatis3DynamicSql生成的代码-->
<dependency>
<groupId>org.mybatis.dynamic-sql</groupId>
<artifactId>mybatis-dynamic-sql</artifactId>
<version>1.1.4</version>
</dependency>
<!-- jdbc驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<!-- logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<type>jar</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.0</version>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<phase>package</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<verbose>true</verbose>
<contexts>default</contexts>
<overwrite>false</overwrite>
<configurationFile>${basedir}/src/main/resources/generatorConfig.xml</configurationFile>
<includeCompileDependencies>true</includeCompileDependencies>
</configuration>
</plugin>
</plugins>
</build>
插件参数详解
plugin/configuration 节点可以配置影响 MBG 行为的参数,如下:
代码生成规则配置
使用 maven 插件的方式不需要编写代码,只要将规则配置到 generatorConfig.xml 文件就行,配置内容主要为:
- 如何连接到数据库
- 生成什么对象,以及如何生成它们
- 哪些表将用于对象生成
下面先给一个简单版的,后面再具体讲解这些参数的意义:
<?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">
<generatorConfiguration>
<!--导入配置 -->
<properties resource="jdbc.properties"></properties>
<!-- context 一般是一个数据源一个context -->
<context id="default" targetRuntime="MyBatis3Simple" defaultModelType="flat">
<!-- 插件 -->
<plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin">
<property name="useEqualsHashCodeFromRoot" value="true"/>
</plugin>
<plugin type="org.mybatis.generator.plugins.ToStringPlugin">
<property name="useToStringFromRoot" value="true"/>
</plugin>
<!-- 注释 -->
<commentGenerator type="cn.zzs.mybatis.generator.MyCommentGenerator">
<property name="addRemarkComments" value="true"/>
<property name="dateFormat" value="yyyy-MM-dd HH:mm:ss"/>
</commentGenerator>
<!--jdbc的数据库连接 -->
<jdbcConnection
driverClass="${jdbc.driver}"
connectionURL="${jdbc.url}"
userId="${jdbc.username}"
password="${jdbc.password}">
</jdbcConnection>
<!-- 类型解析器 -->
<javaTypeResolver>
<property name="forceBigDecimals" value="true"/>
</javaTypeResolver>
<!-- 实体类 -->
<javaModelGenerator
targetPackage="cn.zzs.mybatis.entity"
targetProject=".\src\main\java">
<!-- <property name="rootClass" value="cn.zzs.mybatis.entity.EntityClass"/> -->
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- xml -->
<sqlMapGenerator
targetPackage="cn.zzs.mybatis.mapper"
targetProject=".\src\main\resources">
</sqlMapGenerator>
<!-- Mapper接口 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="cn.zzs.mybatis.mapper"
targetProject=".\src\main\java">
<!-- <property name="rootInterface" value="cn.zzs.mybatis.entity.BaseMapper"/> -->
</javaClientGenerator>
<!-- 指定数据库表 -->
<table tableName="demo_%" >
<domainObjectRenamingRule searchString="^Demo" replaceString=""/>
</table>
</context>
</generatorConfiguration>
命令执行
maven 插件的运行方式
maven build,输入mybatis-generator:generate
,生成成功。
Java程序的运行方式
通常情况下,我们都是使用 maven 插件的方式,但是,当我们在 MBG 中指定了自定义的实现,使用 maven 插件可能会报错,这个时候就需要通过 Java 程序的方式运行 MBG,具体方法如下:
public static void main(String[] args) throws Exception {
// LogFactory.forceSlf4jLogging();
// System.setProperty("user.name", "zzs");
// 这个集合记录着生成、合并、覆盖文件的信息
List<String> warnings = new ArrayList<String>();
InputStream in = MybatisGenerator.class.getClassLoader().getResourceAsStream("generatorConfig.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(in);
// 不覆盖 Java 文件
boolean overwrite = false;
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
// 生成文件
myBatisGenerator.generate(null);
// 打印信息
warnings.forEach(System.err::println);
}
生成规则详解
generatorConfig.xml 的顶层结构如下:
- generatorConfiguration(配置)
- classPathEntry(JDBC驱动路径)
- properties(properties文件路径)
- context(生成对象的环境)
- property(Context作用域参数)
- jdbcConnection(JDBC连接)
- connectionFactory(JDBC连接工厂)
- commentGenerator(注释生成器)
- javaModelGenerator(实体对象生成器)
- javaClientGenerator(Mapper 接口或实现类生成器)
- sqlMapGenerator(xml 生成器)
- table(用于生成对象的表)
- javaTypeResolver(Java 类型处理器)
- plugin(插件)
下面选择部分节点展开分析:
context*
<context>
节点用于指定生成一系列对象的环境。我们可以在配置文件中配置多个<context>
节点来实现从不同数据源或采用不同生成参数生成对象。
属性
这里最重要的属性是targetRuntime,它直接决定该环境下生成的代码风格,常用的风格为 MyBatis3DynamicSql 和 MyBatis3Simple。
子节点
<context>
包含以下子节点:
<property>
(0..N)<plugin>
(0..N)<commentGenerator>
(0 or 1)<connectionFactory>/<jdbcConnection>
(1 Required)<javaTypeResolver>
(0 or 1)<javaModelGenerator>
(1 Required)<sqlMapGenerator>
(0 or 1)<javaClientGenerator>
(0 or 1)<table>
(1..N)
其中,<property>
支持以下参数。其中,针对 mysql 数据库,可以将定界符修改为反单引号。如果想要使用自定义的代码格式或 XML 格式,可以配置自定义实现。
jdbcConnection
<jdbcConnection>
节点用于指定 MBG 进行数据库交互所用的 JDBC 连接。注意,<jdbcConnection>
和<jdbcConnection>
节点只要一个就行了。
属性
子节点
<property>
(0..N):用于定义 JDBC 驱动所需的一些参数,较少用到。
connectionFactory
<jdbcConnection>
节点用于指定和配置 MBG 进行数据库交互时获取 JDBC 连接的工厂。注意,<jdbcConnection>
和<jdbcConnection>
节点只要一个就行了。
属性
子节点
<jdbcConnection>
的子节点为<property>
(0..N),参数如下表。如果是使用默认的连接工厂,这几个参数必须按照下表的提供,但是,如果是自定义的连接工厂,需要提供什么参数由你自己决定。
如何使用HikariCP作为 MBG 的连接工厂
这里我简单的实现了一个连接工厂:
public class HikariConnectionFactory implements ConnectionFactory {
private DataSource dataSource;
public HikariConnectionFactory() {
super();
HikariConfig config = new HikariConfig("/hikari.properties");
dataSource = new HikariDataSource(config);
}
@Override
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
@Override
public void addConfigurationProperties(Properties properties) {
// do nothing
}
}
将它配置到 XML 文件中,这时我们不需要添加任何的<property>
子节点。
<connectionFactory type="cn.zzs.mybatis.factory.HikariConnectionFactory"/>
使用 Java 程序运行 MBG,可以看到代码可以正常生成。
commentGenerator*
<commentGenerator>
节点用于指定和配置 Java 代码或 XML 文件中使用的注释生成器。默认的注释生成器比较鸡肋,一般我们都会考虑自己实现。
属性
子节点
<commentGenerator>
的子节点为<property>
(0..N),参数如下表。注意,这些参数是针对默认注释生成器的,如果是自定义的,需要提供什么参数由你自己决定。
自定义注释生成器
想要自定义注释生成器需要实现org.mybatis.generator.api.CommentGenerator
接口并提供无参构造,在本项目中,我在DefaultCommentGenerator
的基础上改造了一个注释生成器,感兴趣的可以移步到项目源码。
注意,编写自定义注解生成器时应该考虑在xml节点的注释中加入@mbg.generated来维持 MBG 文件合并的功能。
下面简单举个例子,MBG 生成的实体类属性的注解是这样的:
/**
* Database Column Remarks:
* 员工id
*
* This field was generated by MyBatis Generator.
* This field corresponds to the database column demo_employee.id
*
* @mbg.generated Sat May 02 12:52:28 CST 2020
*/
private String id;
但是,我不想要这种注释,这时,我们可以改造以下方法:
@Override
public void addFieldComment(Field field, IntrospectedTable introspectedTable,
IntrospectedColumn introspectedColumn) {
if (suppressAllComments) {
return;
}
field.addJavaDocLine("/**");
// 获取列注释并加入到注解中
String remarks = introspectedColumn.getRemarks();
if (addRemarkComments && StringUtility.stringHasValue(remarks)) {
String[] remarkLines = remarks.split(System.getProperty("line.separator"));
for (String remarkLine : remarkLines) {
field.addJavaDocLine(" * <p>" + remarkLine + "</p>");
}
}
field.addJavaDocLine(" */");
}
并且在 XML 中进行如下配置:
<!-- 注释 -->
<commentGenerator type="cn.zzs.mybatis.generator.MyCommentGenerator">
<property name="addRemarkComments" value="true"/>
</commentGenerator>
使用 Java 程序运行 MBG,可以看到实体类属性的注释变成我们想要的样子。
/**
* <p>员工id</p>
*/
private String id;
javaTypeResolver
<javaTypeResolver>
节点用于指定和配置 Java 类型解析器。默认的解析器可能会将数据库类型 decimal 或 numberic 解析为Short
、Integer
、Long
等 Java 类型,如果我们不希望这样解析,就需要使用到这个节点。
Java 类型解析器使用默认的就行,一般不会去重写它。
属性
子节点
<javaTypeResolver>
的子节点为<property>
(0..N),参数如下表。注意,这些参数是针对默认注释生成器的,如果是自定义的,需要提供什么参数由你自己决定。
javaModelGenerator
<javaModelGenerator>
节点用于配置实体类生成器。实体类生成器不支持自定义。
属性
子节点
<javaModelGenerator>
的子节点为<property>
(0..N),参数如下表。用的比较多的是 rootClass 和 trimStrings。
javaClientGenerator
<javaClientGenerator>
节点用于指定和配置客户端类生成器。这个节点是可选的,如果不指定,则不会生成客户端类。客户端类一般指的是 Mapper 接口及 Mapper 接口的一些辅助类,例如SqlProvider
。
属性
下面的 type 属性仅针对 MyBatis3Simple 和 MyBatis3 风格生效,而且 MyBatis3Simple 风格不支持 MIXEDMAPPER。
子节点
<javaClientGenerator>
的子节点为<property>
(0..N),参数如下表。注意,这些参数是针对默认注释生成器的,如果是自定义的,需要提供什么参数由你自己决定。
sqlMapGenerator
<sqlMapGenerator>
节点用于配置 XML 生成器,不支持自定义。
属性
子节点
<sqlMapGenerator>
的子节点为<property>
(0..N),参数如下表。
table*
<table>
节点用于指定需要用于生成对象的表以及配置某些生成规则。这个节点相比前面提到的,要更加复杂一些。
属性
<table>
节点支持的属性很多,一般保持默认就可以了。
子节点
<table>
的子节点如下:
<property>
(0..N)<generatedKey>
(0 or 1)<domainObjectRenamingRule>
(0 or 1)<columnRenamingRule>
(0 or 1)<columnOverride>
(0..N)<ignoreColumn>
(0..N)<ignoreColumnsByRegex>
(0..N)
这几个子节点中,常用到的是<domainObjectRenamingRule>
,其他的用的比较少。下面挑几个来分析下:
property
<property>
配置的大部分参数都是为了覆盖全局的配置,并不常用。
domainObjectRenamingRule/columnRenamingRule
<domainObjectRenamingRule>
节点一般用于重命名实体类类名。
例如,在没有指定 domainObjectName 的情况下,demo_employee 的表将生成实体类 DemoEmloyee,但我希望去掉前面的 Demo 前缀,则可以这样处理:
<table tableName="demo_%" >
<!-- replaceString属性可以省略 -->
<domainObjectRenamingRule searchString="^Demo" replaceString=""/>
</table>
另一个子节点<columnRenamingRule>
也是相同的用法。
<table tableName="demo_%" >
<columnRenamingRule searchString="^Employee_" replaceString="" />
</table>
ignoreColumnsByRegex/ignoreColumn
<ignoreColumnsByRegex>
和<ignoreColumn>
节点用于告诉 MBG 生成代码时忽略某些列。使用方法如下。
<table tableName="Foo">
<ignoreColumnsByRegex pattern="(?i)col.*">
<except column="col01"/>
<except column="col13"/>
</ignoreColumnsByRegex>
</table>
plugin*
<plugin>
节点用于定义和配置插件。这个节点只有一个 type 属性,用于指定使用哪个插件,并通过子节点 property 来为这个插件设置参数。
如果自定义的话需要实现org.mybatis.generator.api.Plugin
接口,并提供无参构造。MBG 为我们提供了许多好用的插件,如下:
插件的配置方式非常简单,如下:
<!-- 插件 -->
<plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin">
<property name="useEqualsHashCodeFromRoot" value="true"/>
</plugin>
<plugin type="org.mybatis.generator.plugins.ToStringPlugin">
<property name="useToStringFromRoot" value="true"/>
</plugin>
运行 Java 程序,可以看到实体类中生成类 toString 、hashCode 和 equals 方法。
以上,基本讲完 MBG 的使用方法,涉及到的内容可以满足实际使用需求。
MyBatis3DynamicSql 风格 API 的使用
在研究 MBG 之前,其实我并没有听说过 MyBatis3DynamicSql 风格,因为项目里一直使用的是 Mybatis3Simple,网上也很少人提起。
Mybatis3Simple 可以生成简单的 CRUD,但是针对高级条件查询就无能为力了,实际项目中,我们有时必须手动地去编写高级查询的代码,当然,我们也可以自定义代码生成器来生成,但是这也只能针对单表,涉及到多表时,就不尽人意了。
MyBatis3DynamicSql 风格丢弃了 XML 文件,使用全注解形式并搭配几个条件辅助类,刚接触时,我还是比较抗拒,因为前面MyBatis3 中也尝试过全注解和条件类,当遇到某些复杂场景时,还是需要 XML,而且生成器提供的条件类会渗透到服务层。
直到开始使用 MyBatis3DynamicSql,我才发现它的强大。它可以做到:
- 单表高级查询。包括 Equal/NotEqual、Like/NotLike、In/NotIn、Between/NotBetween、IsNull/IsNotNull 等等,而且还可以判空设置条件。
- 联表查询。你可以像给单表一样给关联表设置条件。
- 分组、排序、分页。
- 只返回你要的字段。
下面这个例子,涉及到了关联查询、排序、分页,而 MyBatis3DynamicSql 都能帮我们处理,并且它利用 Lambda表达式来解耦条件类。
@Test
public void testSelect() {
// 注意,当查询结果多于1时会报错
List<Employee> lsit = baseMapper.select(c ->
c.leftJoin(DepartmentDynamicSqlSupport.department)
.on(departmentId, new EqualTo(DepartmentDynamicSqlSupport.id))
.where(name, isLikeWhenPresent("zzs%"), or(name, isLikeWhenPresent("zzf%")))
.and(status, isEqualTo((byte)1))
.and(address, isIn("北京", "广东"))
.and(DepartmentDynamicSqlSupport.name, isEqualToWhenPresent("质控部"))
.orderBy(gmtCreate.descending())
.limit(3)
.offset(1)
);
lsit.forEach(System.err::println);
}
我相信,MyBatis3DynamicSql 风格会被更多开发者使用,这里就不长篇大论的讲解如何使用它,因为它的 API 并不难操作。感兴趣的朋友可以移步到项目源码阅读:测试例子