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
                            &amp;characterEncoding=utf8&amp;zeroDateTimeBehavior=convertToNull
                            &amp;useSSL=true&amp;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();
        }
    }
}
posted @ 2022-10-13 15:40  浪迹天涯的派大星  阅读(1259)  评论(0)    收藏  举报