如何使用MyBatis Generator

你是不是正在遇到以下问题

  1. 在采用MyBatis框架时,每新增一个表,都需要对着表手工写一遍数据库模型类,手工新增相应的mapper接口类,手工新增相应的mapper.xml文件;
  2. 手工编写xml文件时,有时还会漏了映射某个字段,或者写错了某些字母符合,然后到启动项目后才发现。

MyBatis Generator 可以高效帮你解决这些问题。

什么是MyBatis Generator

MyBatis Generator是MyBatis官方提供的一个代码生成工具,通过少量的配置就能帮助我们生成好数据库对应的Java持久化对象、操作数据库的接口方法以及对应的SQL的mapper。

如何使用

环境准备

本文采用组件版本如下:

1
2
3
4
MySQL数据库:5.7.28
mysql-connector-java:8.0.30
mybatis-generator-core:1.4.1
spring-boot: 2.7.1

使用的库表为MySQL官方网站提供的库表样例,sakila库film表。本文将采用MyBatis Generator为film表自动生成一系列相关代码。

配置

MyBatis Generator既可以下载jar包通过命令行的方式运行,也可以通过Ant,maven的方式进行代码生成。这里采用maven的方式。这里分两步来进行:

配置文件

总体配置及注释如下:

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
<!DOCTYPE generatorConfiguration PUBLIC
"-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>

<!-- 1、引入外部配置文件,可选。引入后,可以通过${key}的方式进行配置值的获取 -->
<properties resource="application.properties" />

<!--
2、配置context。
id唯一即可。
targetRuntime默认值为MyBatis3,会影响dao和mapper.xml生成的内容。
defaultModelType默认值为conditional,flat表示一张表对应一个实体类。
此处选择MyBatis3Simple,生成的接口代码简洁很多,个人比较喜欢这个,
因为MyBatis的最大优势本来就是可以自由编写sql脚本。
-->
<context id="simple" targetRuntime="MyBatis3Simple" defaultModelType="flat">

<!-- 3、插件配置 -->
<!-- 自定义lombok插件,使用后将不会生成setter/getter方法,而是用Lombok注解 -->
<plugin type="org.jiapengcai.lab.spring.common.plugin.LombokPlugin" />
<!-- 覆盖生成XML文件,如果现有的xml文件里已有自定义的sql接口,要慎用 -->
<plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" />

<!-- 4、注释生成配置 -->
<commentGenerator>
<!-- 是否要跳过生成日期。每个字段都会生成日期,而且即使修改了一个字段,其它字段也会跟着变换日期 -->
<property name="suppressDate" value="true"/>
<!-- 是否要添加表中的字段注释 -->
<property name="addRemarkComments" value="true"/>
<!-- 是否要跳过生成注释,这个看个人喜好 -->
<property name="suppressAllComments" value="false"/>
</commentGenerator>

<!-- 5、数据库链接配置 -->
<jdbcConnection driverClass="${spring.datasource.driver-class-name}"
connectionURL="${spring.datasource.url}"
userId="${spring.datasource.username}"
password="${spring.datasource.password}"/>

<!-- 6、java类型转换配置 -->
<!-- CustomJavaTypeResolve包含里自定义的JDBC与Java类型的转换 -->
<javaTypeResolver type="org.jiapengcai.lab.spring.common.config.CustomJavaTypeResolver">
<!-- 是否使用BigDecimal,默认为false,如果开启后,JDBC类型为DECIMAL和NUMERIC的将都会转换为java.math.BigDecimal -->
<property name="forceBigDecimals" value="true"/>
<!--
是否使用JSR-310类型,默认为false,如果开启后,会做以下转换:
JDBC类型 Java类型
DATE java.time.LocalDate
TIME java.time.LocalTime
TIMESTAMP java.time.LocalDateTime
注:要注意pom文件里要引入org.mybatis.mybatis-typehandlers-jsr310依赖
-->
<property name="useJSR310Types" value="true"/>
</javaTypeResolver>

<!-- 7、生成模型的包名和位置 -->
<javaModelGenerator targetPackage="org.jiapengcai.lab.spring.mbg.model" targetProject="src/main/java"/>

<!-- 8、生成映射文件的包名和位置-->
<sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources" />

<!-- 9、生成mapper接口类的包名和位置,如果想生成注解形式的mapper,type的值填ANNOTATEDMAPPER-->
<javaClientGenerator type="XMLMAPPER" targetPackage="org.jiapengcai.lab.spring.mbg.mapper" targetProject="src/main/java"/>

<!-- 10、需要生成哪些表,需要生成多少个表就相应写多少个table标签,domainObjectName为生成的实体类名-->
<table tableName="film" domainObjectName="Film"/>
</context>
</generatorConfiguration>

说明

  1. 生成的数据库模型实体类会包含各个字段的setter/getter方法,个人习惯了用lombok,感觉不是很美观,Github上有个大神写了一个lombok的插件,这里用上了。如果不喜欢可以去掉。
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
package org.jiapengcai.lab.spring.common.plugin;

import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.TopLevelClass;

import java.util.*;

/**
* @author caijiapeng
* @date 2022-08-28 21:58
*/
public class LombokPlugin extends PluginAdapter {

private final Collection<Annotations> annotations;

/**
* LombokPlugin constructor
*/
public LombokPlugin() {
annotations = new LinkedHashSet<Annotations>(Annotations.values().length);
}

/**
* @param warnings list of warnings
* @return always true
*/
public boolean validate(List<String> warnings) {
return true;
}

/**
* Intercepts base record class generation
*
* @param topLevelClass the generated base record class
* @param introspectedTable The class containing information about the table as
* introspected from the database
* @return always true
*/
@Override
public boolean modelBaseRecordClassGenerated(
TopLevelClass topLevelClass,
IntrospectedTable introspectedTable
) {
addAnnotations(topLevelClass);
return true;
}

/**
* Intercepts primary key class generation
*
* @param topLevelClass the generated primary key class
* @param introspectedTable The class containing information about the table as
* introspected from the database
* @return always true
*/
@Override
public boolean modelPrimaryKeyClassGenerated(
TopLevelClass topLevelClass,
IntrospectedTable introspectedTable
) {
addAnnotations(topLevelClass);
return true;
}

/**
* Intercepts "record with blob" class generation
*
* @param topLevelClass the generated record with BLOBs class
* @param introspectedTable The class containing information about the table as
* introspected from the database
* @return always true
*/
@Override
public boolean modelRecordWithBLOBsClassGenerated(
TopLevelClass topLevelClass,
IntrospectedTable introspectedTable
) {
addAnnotations(topLevelClass);
return true;
}

/**
* Prevents all getters from being generated.
* See SimpleModelGenerator
*
* @param method the getter, or accessor, method generated for the specified
* column
* @param topLevelClass the partially implemented model class
* @param introspectedColumn The class containing information about the column related
* to this field as introspected from the database
* @param introspectedTable The class containing information about the table as
* introspected from the database
* @param modelClassType the type of class that the field is generated for
*/
@Override
public boolean modelGetterMethodGenerated(
Method method,
TopLevelClass topLevelClass,
IntrospectedColumn introspectedColumn,
IntrospectedTable introspectedTable,
ModelClassType modelClassType
) {
return false;
}

/**
* Prevents all setters from being generated
* See SimpleModelGenerator
*
* @param method the setter, or mutator, method generated for the specified
* column
* @param topLevelClass the partially implemented model class
* @param introspectedColumn The class containing information about the column related
* to this field as introspected from the database
* @param introspectedTable The class containing information about the table as
* introspected from the database
* @param modelClassType the type of class that the field is generated for
* @return always false
*/
@Override
public boolean modelSetterMethodGenerated(
Method method,
TopLevelClass topLevelClass,
IntrospectedColumn introspectedColumn,
IntrospectedTable introspectedTable,
ModelClassType modelClassType
) {
return false;
}

/**
* Adds the lombok annotations' imports and annotations to the class
*
* @param topLevelClass the partially implemented model class
*/
private void addAnnotations(TopLevelClass topLevelClass) {
for (Annotations annotation : annotations) {
topLevelClass.addImportedType(annotation.javaType);
topLevelClass.addAnnotation(annotation.asAnnotation());
}
}

@Override
public void setProperties(Properties properties) {
super.setProperties(properties);

//@Data is default annotation
annotations.add(Annotations.DATA);

for (String annotationName : properties.stringPropertyNames()) {
if (annotationName.contains(".")) {
// Not an annotation name
continue;
}
String value = properties.getProperty(annotationName);
if (!Boolean.parseBoolean(value)) {
// The annotation is disabled, skip it
continue;
}
Annotations annotation = Annotations.getValueOf(annotationName);
if (annotation == null) {
continue;
}
String optionsPrefix = annotationName + ".";
for (String propertyName : properties.stringPropertyNames()) {
if (!propertyName.startsWith(optionsPrefix)) {
// A property not related to this annotation
continue;
}
String propertyValue = properties.getProperty(propertyName);
annotation.appendOptions(propertyName, propertyValue);
annotations.add(annotation);
annotations.addAll(Annotations.getDependencies(annotation));
}
}
}

public boolean clientGenerated(
Interface interfaze,
TopLevelClass topLevelClass,
IntrospectedTable introspectedTable
) {
interfaze.addImportedType(new FullyQualifiedJavaType(
"org.apache.ibatis.annotations.Mapper"));
interfaze.addAnnotation("@Mapper");
return true;
}

private enum Annotations {
DATA("data", "@Data", "lombok.Data"),
BUILDER("builder", "@Builder", "lombok.Builder"),
ALL_ARGS_CONSTRUCTOR("allArgsConstructor", "@AllArgsConstructor", "lombok.AllArgsConstructor"),
NO_ARGS_CONSTRUCTOR("noArgsConstructor", "@NoArgsConstructor", "lombok.NoArgsConstructor"),
ACCESSORS("accessors", "@Accessors", "lombok.experimental.Accessors"),
TO_STRING("toString", "@ToString", "lombok.ToString");


private final String paramName;
private final String name;
private final FullyQualifiedJavaType javaType;
private final List<String> options;


Annotations(String paramName, String name, String className) {
this.paramName = paramName;
this.name = name;
this.javaType = new FullyQualifiedJavaType(className);
this.options = new ArrayList<String>();
}

private static Annotations getValueOf(String paramName) {
for (Annotations annotation : Annotations.values())
if (String.CASE_INSENSITIVE_ORDER.compare(paramName, annotation.paramName) == 0)
return annotation;

return null;
}

private static Collection<Annotations> getDependencies(Annotations annotation) {
if (annotation == ALL_ARGS_CONSTRUCTOR)
return Collections.singleton(NO_ARGS_CONSTRUCTOR);
else
return Collections.emptyList();
}

// A trivial quoting.
// Because Lombok annotation options type is almost String or boolean.
private static String quote(String value) {
if (Boolean.TRUE.toString().equals(value) || Boolean.FALSE.toString().equals(value))
// case of boolean, not passed as an array.
return value;
return value.replaceAll("[\\w]+", "\"$0\"");
}

private void appendOptions(String key, String value) {
String keyPart = key.substring(key.indexOf(".") + 1);
String valuePart = value.contains(",") ? String.format("{%s}", value) : value;
this.options.add(String.format("%s=%s", keyPart, quote(valuePart)));
}

private String asAnnotation() {
if (options.isEmpty()) {
return name;
}
StringBuilder sb = new StringBuilder();
sb.append(name);
sb.append("(");
boolean first = true;
for (String option : options) {
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append(option);
}
sb.append(")");
return sb.toString();
}
}
}
  1. 有些生成的字段类型可能并不是我们想要的,比如对于数据库的smallint类型的字段,生成的实体类的字段类型为Short,如果我们想转换成Integer,需要自定义转换处理器,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.jiapengcai.lab.spring.common.config;

import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl;

import java.sql.Types;

/**
* @author caijiapeng
* @date 2022-03-28 14:53
*/
public class CustomJavaTypeResolver extends JavaTypeResolverDefaultImpl {

public CustomJavaTypeResolver() {
super();
typeMap.put(Types.SMALLINT, new JdbcTypeInformation("SMALLINT",
new FullyQualifiedJavaType(Integer.class.getName())));
}
}

pom文件配置

pom文件配置如下:

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>demo</artifactId>
<groupId>org.jiapengcai.lab.spring</groupId>
<version>1.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-lab-mybatis-generator</artifactId>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
</dependency>
<dependency>
<groupId>org.jiapengcai.lab.spring</groupId>
<artifactId>spring-lab-common</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>
<configuration>
<!--mybatis的代码生成器的配置文件-->
<configurationFile>src/main/resources/mybatis-generator-config.xml</configurationFile>
<!--允许覆盖生成的文件,只覆盖生成的实体类和mapper接口类,mapper xml文件不会覆盖-->
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
</build>

</project>

注意: 自定义的插件和类型转换器应该放到不同的包里,然后引入到maven插件配置里,不然会报无法初始化的错误。本文中的LombokPlugin以及CustomJavaTypeResolver放在了common包里。

注意坑

配置文件里的context配置项的子元素是有顺序要求的,如果某个子元素没有按顺序进行配置,可能会出现配置不生效的问题:

  1. property (0个或多个)
  2. plugin (0个或多个)
  3. commentGenerator (0个或1个)
  4. connectionFactory 和 jdbcConnection,二选一进行配置
  5. javaTypeResolver (0个或1个)
  6. javaModelGenerator(必须配置1个)
  7. sqlMapGenerator (0个或1个)
  8. javaClientGenerator (0个或1个)
  9. table (1个或多个)

参考

  1. MyBatis Generator官方文档
  2. MyBatis Generator插件列表
  3. MyBatis Generator targetRuntime 类型
  4. MySQL官方数据库数据样例
  5. MyBatis Generator Lombok插件