mybatis-generator使用
实际开发中,关于数据库操作方面的代码,例如,Entity、Mapper、Xml文件基本都是自动生成,之前一直是使用idea的插件easyCode生成,这个插件只能在idea旗舰版中使用,基本可以自定义代码模板,比较好用,配置好后是界面图形化的操作,生成需要的代码,本次由于项目原因,不能使用easyCode插件,所以用到了mybatis-generator的方式,正好研究了一下,记录一下里面的细节,下面进入正文。
1、引入依赖
新建一个springboot项目,这里不多说,直接贴出对应pom的依赖
<dependencies>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.0</version>
</dependency>
<!--版本尽量和实际使用的数据库版本一致-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!--非必需,为了使用lombok注解-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--非必需,为了使用其中的json工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.17</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!--Mybatis-generator插件,用于自动生成Mapper和POJO-->
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.0</version>
<configuration>
<!--mybatis-generator配置文件的位置-->
<configurationFile>src/main/resources/mybatis-generator.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<!--引入依赖generator和mysql-->
<dependencies>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
2、编写mybatis-generator的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>
<context id="context" targetRuntime="MyBatis3">
<!--针对表名和列名使用,默认是双引号(见org.mybatis.generator.config中的属性),在mysql中得是反单引号-->
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<property name="javaFileEncoding" value="UTF-8"/>
<!--相关插件,自定义插件也可以放在这里-->
<!--生成mapper.xml时覆盖原文件-->
<plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"></plugin>
<!--为entity生成序列化方法-->
<plugin type="org.mybatis.generator.plugins.SerializablePlugin"></plugin>
<!--自定义的Lombok插件-->
<plugin type="org.example.common.plugin.MyGeneratorPlugin"></plugin>
<!--自定义生成Entity和Mapper的代码注释,由于生成的注释没有什么含义(相同的字符),所以默认选择删除,通过插件自定义生成-->
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
<property name="addRemarkComments" value="true"/>
</commentGenerator>
<!--数据库链接地址账号密码-->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true
&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull
&useSSL=true&serverTimezone=GMT%2B8"
userId="root"
password="root">
<property name="nullCatalogMeansCurrent" value="true"/>
<property name="useInformationSchema" value="true"/>
</jdbcConnection>
<!--非必需,类型处理器,数据库类型和java类型之间的转换控制,大部分类型都有默认对应关系,除非想自定义控制-->
<!--默认的映射类型见,org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl-->
<javaTypeResolver type="org.example.common.resolver.MyJavaTypeResolver">
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!--生成Model类存放位置-->
<!--targetPackage就是包名,targetProject需要到除最顶层的项目名称外的第二级model,在此处就是到start模块-->
<javaModelGenerator targetPackage="org.example.entity" targetProject="start\src\main\java">
<!--是否允许生成子包,即targetPackage.schemaName.tableName,否则直接在下面生成entity-->
<property name="enableSubPackages" value="false"/>
<!--是否添加构造方法,这里采用了lombok,所以不需要-->
<property name="constructorBased" value="false"/>
<!--在get方法中,对String字符串调用trim()方法-->
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!--生成映射文件存放位置-->
<sqlMapGenerator targetPackage="mapper" targetProject="start\src\main\resources">
<property name="enableSubPackages" value="false"/>
</sqlMapGenerator>
<!--生成Dao类存放位置-->
<javaClientGenerator type="XMLMAPPER" targetPackage="org.example.dao" targetProject="start\src\main\java">
<property name="enableSubPackages" value="false"/>
</javaClientGenerator>
<!--指定需要生成对应表及类名-->
<table tableName="change_record"
domainObjectName="ChangeRecord"
enableCountByExample="false"
enableUpdateByExample="false"
enableDeleteByExample="false"
enableSelectByExample="false"
selectByExampleQueryId="false">
<!--是否使用数据库真实的字段名,为false将自动转驼峰-->
<property name="useActualColumnNames" value="false"/>
</table>
</context>
</generatorConfiguration>
上面有一些是需要根据自己的实际情况修改的:
a、数据库的url、用户名和密码;
b、<plugin/>自定义的插件,上面的文档中,第三个就是自定义的lombok插件(具体代码见下面,不想自定义插件,可以删除掉)
c、插件生成Entity、Mapper、Xml的位置,这个看上面的注释自行修改
d、根据自己的数据库指定需要生成的表名和对应的类名
3、实现自定义的插件(非必需)
1、由于不想使用其原有的get和set方法,所以自定义实现了插件,类上加了Lombok注解
2、原有生成的Entity注释没有意义,目前是将数据库字段的描述作为java属性字段的注释。
具体的插件实现如下:
package org.example.common.plugin;
import cn.hutool.json.JSONUtil;
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.*;
import org.mybatis.generator.internal.util.StringUtility;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
/**
* 字段和类生成时,会调用PluginAdapter中的方法,所以可以按需求重写对应的方法
*/
public class MyGeneratorPlugin extends PluginAdapter {
private static final String SEPARATION = ",";
private static final String JSON_PREFIX = "{";
private static final String JSON_SUFFIX = "}";
private boolean makeConstant = true;
@Override
public boolean validate(List<String> list) {
return true;
}
/**
* 每个字段不生成get方法
*/
@Override
public boolean modelGetterMethodGenerated(Method method,
TopLevelClass topLevelClass,
IntrospectedColumn introspectedColumn,
IntrospectedTable introspectedTable,
ModelClassType modelClassType) {
return false;
}
/**
* 每个字段不生成set方法
*/
@Override
public boolean modelSetterMethodGenerated(Method method,
TopLevelClass topLevelClass,
IntrospectedColumn introspectedColumn,
IntrospectedTable introspectedTable,
ModelClassType modelClassType) {
return false;
}
/**
* 类上增加对应的注解,并生成类注释
*/
@Override
public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass,
IntrospectedTable introspectedTable) {
// 到入需要注解的包
topLevelClass.addImportedType("lombok.Data");
topLevelClass.addImportedType("lombok.Builder");
topLevelClass.addImportedType("lombok.NoArgsConstructor");
topLevelClass.addImportedType("lombok.AllArgsConstructor");
// 添加domain的注解
topLevelClass.addAnnotation("@Data");
topLevelClass.addAnnotation("@Builder");
topLevelClass.addAnnotation("@NoArgsConstructor");
topLevelClass.addAnnotation("@AllArgsConstructor");
// 添加domain的注释
topLevelClass.addJavaDocLine("/**");
topLevelClass.addJavaDocLine("* @ClassName: " + topLevelClass.getType().getShortName());
topLevelClass.addJavaDocLine("* @Description: ");
topLevelClass.addJavaDocLine("* @author: Mybatis Generator");
topLevelClass.addJavaDocLine("* @date " + date2Str(new Date()));
topLevelClass.addJavaDocLine("*/");
return true;
}
/**
* 生成属性字段注释(将数据库字段描述作为属性注释)
*/
@Override
public boolean modelFieldGenerated(Field field,
TopLevelClass topLevelClass,
IntrospectedColumn introspectedColumn,
IntrospectedTable introspectedTable,
ModelClassType modelClassType) {
this.comment(field, topLevelClass, introspectedColumn, introspectedTable);
return true;
}
private void comment(JavaElement element,
TopLevelClass topLevelClass,
IntrospectedColumn introspectedColumn,
IntrospectedTable introspectedTable) {
element.getJavaDocLines().clear();
element.addJavaDocLine("/**");
//获取列字段注解
String remark = introspectedColumn.getRemarks();
if (remark != null && remark.length() > 1) {
element.addJavaDocLine(" * " + remark);
}
// mysql 中自增主键,添加 AutoIncrement: true 说明
boolean autoIncrement = introspectedColumn.isAutoIncrement();
if (autoIncrement) {
element.addJavaDocLine(" * AutoIncrement: true");
}
element.addJavaDocLine(" */");
if (this.makeConstant) {
System.out.println("start constant " + introspectedColumn.getActualColumnName());
if (StringUtility.stringHasValue(remark) && remark.contains(JSON_PREFIX) && remark.contains(JSON_SUFFIX)) {
//截取常量字符串
String commentJson = remark.substring(remark.indexOf(JSON_PREFIX), remark.lastIndexOf(JSON_SUFFIX) + 1);
try {
LinkedHashMap<String, String> commentMap = JSONUtil.toBean(commentJson, LinkedHashMap.class);
commentMap.forEach((key, value) -> {
//常量字段属性名 以列字段名+_+json key 值为字段名
String name = introspectedColumn.getActualColumnName().toUpperCase() + "_" + key.toUpperCase();
//设置常量字段类型与列字段一致
Field field = new Field(name, introspectedColumn.getFullyQualifiedJavaType());
field.setStatic(true);
field.setFinal(true);
//常量字段属性描述
String desc = "";
//常量字段属性值
String constant = "";
if (value.contains(SEPARATION)) {
String[] split = value.split(SEPARATION);
constant = split[0];
desc = split[1];
} else {
constant = value;
}
field.setInitializationString(constant);
field.setVisibility(JavaVisibility.PUBLIC);
field.addJavaDocLine("/**");
field.addJavaDocLine("* " + introspectedColumn.getActualColumnName() + ":" + desc);
field.addJavaDocLine("*/");
if (introspectedTable.getTargetRuntime() == IntrospectedTable.TargetRuntime.MYBATIS3_DSQL) {
context.getCommentGenerator().addFieldAnnotation(field, introspectedTable,
topLevelClass.getImportedTypes());
} else {
context.getCommentGenerator().addFieldComment(field, introspectedTable);
}
//将常量加入BD实体类
topLevelClass.addField(field);
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private String date2Str(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
}
4、实现自定义的JavaTypeResolved(非必需)
generator是已经默认实现了数据库字段类型和java字段之间的映射关系,通过源码可以知道映射关系存在typeMap中,所以如果想修改,只需要找到对应的进行覆盖即可。此处是将TINYINT类型映射成为Integer。(默认是映射为Byte类型,使用不方便)
package org.example.common.resolver;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl;
import java.util.Properties;
public class MyJavaTypeResolver extends JavaTypeResolverDefaultImpl {
@Override
public void addConfigurationProperties(Properties properties) {
//调用父类的默认实现
super.addConfigurationProperties(properties);
//覆盖父类中已经放入typeMap中的值,达到自定义修改映射类型的目的
this.typeMap.put(-6, new JdbcTypeInformation("TINYINT", new FullyQualifiedJavaType(Integer.class.getName())));
}
}
5、自动生成代码(1、运行Maven插件方式 或者 2、运行代码方式)
上述完成后,就可以自动生成代码了,方式有两种,1、运行Maven插件 2、运行代码
5.1、运行Maven插件
在未引入自定义的插件时,可以采用此方式,在idea右侧的maven中选择对应模块下的generator插件,双击执行即可,截图如下:
5.2、运行代码
由于运行maven插件方式,只会运行mybatis的org.mybatis.generator.plugins包名下的插件,所以实现的自定义的generator插件不会被执行,所以采用代码的方式运行genarator,代码如下:
package org.example.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.InputStream;
import java.util.ArrayList;
import java.util.List;
public class MybatisGenerator {
public static void main(String[] args) {
MybatisGenerator generator = new MybatisGenerator();
System.out.println(System.getProperty("user.dir"));
System.out.println(generator.getClass().getResource("/").getPath());
generator.run();
}
public void run() {
try {
//mybatis-generator配置文件所在位置
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("mybatis-generator.xml");
List<String> warnings = new ArrayList<>();
ConfigurationParser parser = new ConfigurationParser(warnings);
Configuration config = parser.parseConfiguration(resourceAsStream);
DefaultShellCallback callback = new DefaultShellCallback(true);
MyBatisGenerator generator = new MyBatisGenerator(config, callback, warnings);
generator.generate(null);
for (String warning : warnings) {
System.err.println(">" + warning);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}