美文网首页
定制MyBatis Generator减少mybatis的使用负

定制MyBatis Generator减少mybatis的使用负

作者: 冰鱼飞鸟 | 来源:发表于2021-01-29 22:22 被阅读0次

    MyBatis Generator

    MyBatis Generator 是MyBatis的快速代码生成工具,它将为MyBatis的所有版本生成代码(entity, 基础的CRUD方法的dao, mapper)。尽管已经提供了大量的配置标签,但是每个公司都有自己的代码规范, 那就只能自己上手扩展了。

    一.MyBatis Generator可以通过以下方式运行

    • 用命令行运行MBG
    • 使用ant运行
    • 作为Maven插件
    • 使用Java运行
    • 作为Eclipse的插件

    二. Mybatis Generator使用

    1. 依赖
            <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.16</version>
            </dependency>
    
    1. 配置文件
    <?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="sqlserverTables" targetRuntime="IntrospectedTableMyBatis3CustomIpl">-->
        <context id="sqlserverTables" targetRuntime="MyBatis3Simple">
            <!-- 生成的pojo,将implements Serializable-->
            <plugin type="org.mybatis.generator.plugins.SerializablePlugin" />
    <!--        <plugin type="com.myproject.mbg.CustomPlugin" />-->
    
            <commentGenerator>
                <!-- 是否去除自动生成的注释 true:是 : false:否 -->
                <property name="suppressAllComments" value="true" />
                <property name="addRemarkComments" value="true"/>
            </commentGenerator>
    
            <!-- 数据库链接URL、用户名、密码 -->
            <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                connectionURL="jdbc:mysql://ip/库名"
                userId="***"
                password="***">
                <property name="useInformationSchema" value="true"/>
                <property name="nullCatalogMeansCurrent" value="true"/>
            </jdbcConnection>
    
            <!--
            默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer
                true,把JDBC DECIMAL 和 NUMERIC 类型解析为java.math.BigDecimal
            -->
            <javaTypeResolver type="com.myproject.mbg.CustomJavaTypeResolver">
                <property name="forceBigDecimals" value="false" />
            </javaTypeResolver>
    
            <!--
            生成model模型,对应的包路径,以及文件存放路径(targetProject),targetProject可以指定具体的路径,如./src/main/java,
            也可以使用“MAVEN”来自动生成,这样生成的代码会在target/generatord-source目录下
            -->
            <javaModelGenerator targetPackage="com.myproject.entity" targetProject="myproject/src/main/java">
                <property name="enableSubPackages" value="true"/>
                <!-- 从数据库返回的值被清理前后的空格  -->
                <property name="trimStrings" value="true" />
            </javaModelGenerator>
    
            <!-- 对应的Mapper接口类文件 -->
            <javaClientGenerator targetPackage="com.myproject.dao.mapper" targetProject="myproject/src/main/java" type="ANNOTATEDMAPPER">
                <property name="enableSubPackages" value="true"/>
            </javaClientGenerator>
    
    
            <table tableName="mbg_test" domainObjectName="MbgTest" enableSelectByExample="true" modelType="flat">
                <property name="useActualColumnNames" value="false"/>
                <generatedKey column ="id" sqlStatement ="MYSQL" identity="true"/>
    <!--            <columnOverride column="id" isGeneratedAlways="true"/>-->
                <ignoreColumn column="test_ignore" />
    
                <columnOverride column="status" javaType="com.myproject.entity.enums.MbgTestStatus" typeHandler="com.myproject.core.mybatis.typehandler.IntEnumTypeHandler" />
                <columnOverride column="another_status" javaType="com.myproject.entity.enums.AnotherStatus" typeHandler="com.myproject.core.mybatis.typehandler.IntEnumTypeHandler" />
    
            </table>
    
        </context>
    </generatorConfiguration>
    
    1. 执行,生成代码
      把第二步的xml文件作为configFile传入
            ConfigurationParser cp = new ConfigurationParser(warnings);
            Configuration config = cp.parseConfiguration(configFile);
            DefaultShellCallback callback = new DefaultShellCallback(overwrite);
            MyBatisGenerator myBatisGenerator = null;
            myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
            myBatisGenerator.generate(null);
    

    三.MyBatis Generator XML配置介绍

    配置文件告诉MBG:

    • 如何连接数据库
    • 为哪些表生成对象
    • 生成什么对象(model,client,map xml),以及如何生成它们

    GeneratorXML配置文件:

    1. <context>

    一个context可以看作一个数据库环境。
    可以在<generatorConfiguration> 元素内列出多个<context>元素, 以允许在MyBatis Generator的同一运行中从不同的数据库或具有不同的生成参数生成对象。

    2. <connectionFactory> 或者 <jdbcConnection>

    设置数据库连接。

    3. <javaClientGenerator>

    生成crud,可以设置生成的文件放在哪个包

    3. <javaModelGenerator>

    生成entity,可以设置生成的文件放在哪个包
    一些主要功能

    • 是否生成全字段构造函数
    • 是否immutable,将决定是否生成set方法
    • set方法是否自动trim

    4. <sqlMapGenerator>

    用于定义SQL映射生成器的属性,当选择的javaClientGenerator需要XML时,此元素才是<context>元素的必需子元素

    5. <javaTypeResolver>

    用于定义 Java 类型解析器的属性。Java 类型解析器决定了 数据库列 与 Java类型的对应关系

    6. <commentGenerator>

    定义注释生成器的属性

    7. <domainObjectRenamingRule>

    设置entity类名生成规则
    比如当库中的所有表都有一个公共前缀,但是我们的entity名字里面不想要这个前缀

    8. <table>

    指定表,为该表生成代码(entity, crud方法, mapper)
    一些主要配置项:

    • entity的名字
    • 是否使用真实列名作为属性名(否则就用驼峰)
    • 是否生成根据主键的select|delete|update
    • 是否生成插入语句
    • 是否允许主键查询
    • 是否immutable,将决定是否生成set方法
    8.1 <generatedKey>

    用于指定自动生成的字段(比如自增id)
    如果指定此元素,则MyBatis Generator(MBG)将在SQL映射的生成的<insert>元素内生成一个适当的<selectKey>
    元素(根据sqlStatement不同而不同),insert的时候会把这个值赋值到对象中。

    MySql -> SELECT LAST_INSERT_ID()

    8.2. <columnOverride>
    <columnOverride column="sku_name" property="skuName" javaType="java.lang.String" jdbcType="VARCHAR"
    isGeneratedAlways="false" typeHandler="com.***.***TypeHandler"/>
    
    • property设置java属性名(一般不需要在columnOverride这个标签里面设置,可以在table标签统一设置成驼峰)
    • 制定列与属性的映射关系
    • isGeneratedAlways=true 生成的insert update方法就不会去设置这个字段的值。
    • typeHandler当默认的类型转换不能满足要求的时候可以自定义类型转换
    8.3. <ignoreColumn>

    忽略某列,所有生成的代码里面都不会包含这列。

    8.4. <ignoreColumnsByRegex>

    根据正则忽略列,可以指定except,这样正则就可以写得更加简单了。

     <table tableName="Foo">
          <ignoreColumnsByRegex pattern="(?i)col.*">
            <except column="col01"/>
            <except column="col13"/>
          </ignoreColumnsByRegex>
        </table>
    
    8.5. <columnRenamingRule>

    设置entity中属性名生成规则
    比如当表中的所有列都有一个公共前缀,但是我们的entity里面不想要这个前缀。

    四.目前使用默认的generator存在的问题

    看默认生成的sql和方法,存在一些问题,不太好用

    public interface MbgTestMapper {
        @Delete({
            "delete from mbg_test",
            "where id = #{id,jdbcType=BIGINT}"
        })
        int deleteByPrimaryKey(Long id);
    
        @Insert({
            "insert into mbg_test (status, ",
            "version, created_time, ",
            "another_status, ",
            "msg, long_msg)",
            "values (#{status,jdbcType=TINYINT,typeHandler=com.myproject.core.mybatis.typehandler.IntEnumTypeHandler}, ",
            "#{version,jdbcType=INTEGER}, #{createdTime,jdbcType=TIMESTAMP}, ",
            "#{anotherStatus,jdbcType=TINYINT,typeHandler=com.myproject.core.mybatis.typehandler.IntEnumTypeHandler}, ",
            "#{msg,jdbcType=LONGVARCHAR}, #{longMsg,jdbcType=LONGVARCHAR})"
        })
        @SelectKey(statement="SELECT LAST_INSERT_ID()", keyProperty="id", before=false, resultType=Long.class)
        int insert(MbgTest record);
    
        @Select({
            "select",
            "id, status, version, created_time, another_status, msg, long_msg",
            "from mbg_test",
            "where id = #{id,jdbcType=BIGINT}"
        })
        @Results({
            @Result(column="id", property="id", jdbcType=JdbcType.BIGINT, id=true),
            @Result(column="status", property="status", typeHandler=IntEnumTypeHandler.class, jdbcType=JdbcType.TINYINT),
            @Result(column="version", property="version", jdbcType=JdbcType.INTEGER),
            @Result(column="created_time", property="createdTime", jdbcType=JdbcType.TIMESTAMP),
            @Result(column="another_status", property="anotherStatus", typeHandler=IntEnumTypeHandler.class, jdbcType=JdbcType.TINYINT),
            @Result(column="msg", property="msg", jdbcType=JdbcType.LONGVARCHAR),
            @Result(column="long_msg", property="longMsg", jdbcType=JdbcType.LONGVARCHAR)
        })
        MbgTest selectByPrimaryKey(Long id);
    
        @Select({
            "select",
            "id, status, version, created_time, another_status, msg, long_msg",
            "from mbg_test"
        })
        @Results({
            @Result(column="id", property="id", jdbcType=JdbcType.BIGINT, id=true),
            @Result(column="status", property="status", typeHandler=IntEnumTypeHandler.class, jdbcType=JdbcType.TINYINT),
            @Result(column="version", property="version", jdbcType=JdbcType.INTEGER),
            @Result(column="created_time", property="createdTime", jdbcType=JdbcType.TIMESTAMP),
            @Result(column="another_status", property="anotherStatus", typeHandler=IntEnumTypeHandler.class, jdbcType=JdbcType.TINYINT),
            @Result(column="msg", property="msg", jdbcType=JdbcType.LONGVARCHAR),
            @Result(column="long_msg", property="longMsg", jdbcType=JdbcType.LONGVARCHAR)
        })
        List<MbgTest> selectAll();
    
        @Update({
            "update mbg_test",
            "set status = #{status,jdbcType=TINYINT,typeHandler=com.myproject.core.mybatis.typehandler.IntEnumTypeHandler},",
              "version = #{version,jdbcType=INTEGER},",
              "created_time = #{createdTime,jdbcType=TIMESTAMP},",
              "another_status = #{anotherStatus,jdbcType=TINYINT,typeHandler=com.myproject.core.mybatis.typehandler.IntEnumTypeHandler},",
              "msg = #{msg,jdbcType=LONGVARCHAR},",
              "long_msg = #{longMsg,jdbcType=LONGVARCHAR}",
            "where id = #{id,jdbcType=BIGINT}"
        })
        int updateByPrimaryKey(MbgTest record);
    }
    

    1. 缺少乐观锁支持,导致生成的update,delete方法,容易被误用。

    自定义pluginAdater,生成updateByVersion方法,deleteByVersion方法,原有的update/delete方法就不要生成了,防止误用。
    自动生成的代码如下

        @Update({
        "update mbg_test",
        "set status = #{status,jdbcType=TINYINT,typeHandler=com.myproject.core.mybatis.typehandler.IntEnumTypeHandler},",
        "version = version+1,",
        "created_time = #{createdTime,jdbcType=TIMESTAMP},",
        "another_status = #{anotherStatus,jdbcType=TINYINT,typeHandler=com.myproject.core.mybatis.typehandler.IntEnumTypeHandler},",
        "msg = #{msg,jdbcType=LONGVARCHAR},",
        "long_msg = #{longMsg,jdbcType=LONGVARCHAR}",
        "where id = #{id,jdbcType=BIGINT}",
        "and version = #{version,jdbcType=BIGINT}"
        })
        int updateByIdAndVersion(MbgTest record);
    

    但是这个方法并不会抛出异常,可以考虑再自动生成default方法来处理异常(具体异常类可以在配置文件里面自由修改)

    2. BLOB字段支持还不友好,我们还需要自己写select/update方法 (分别需要实现 带blob字段和不带blob字段的方法)

    自定义pluginAdater
    目前这里只做了生成一个不含blob字段的select语句。可以考虑生成一套不含blob字段的select/update方法。返回值的问题还没想好(是再生成一个不含blob字段的entity还是生成一个dto,或者说直接使用全字段的entity)

    static final String SELECT_FIELDS_WITHOUT_BLOB = "select status, version, created_time, another_status from mbg_test ";
    

    3. 缺少批量操作的方法

    自定义pluginAdater后生成代码如下

    @Insert({
        "<script> ",
        "insert into mbg_test (status, ",
        "version, created_time, ",
        "another_status, ",
        "msg, long_msg)",
        "values ",
        "<foreach collection='records' item='record' index='index' separator=','> ",
        "(",
        "#{record.status,jdbcType=TINYINT,typeHandler=com.myproject.core.mybatis.typehandler.IntEnumTypeHandler}, ",
        "#{record.version,jdbcType=BIGINT}, #{record.createdTime,jdbcType=TIMESTAMP}, ",
        "#{record.anotherStatus,jdbcType=TINYINT,typeHandler=com.myproject.core.mybatis.typehandler.IntEnumTypeHandler}, ",
        "#{record.msg,jdbcType=LONGVARCHAR}, #{record.longMsg,jdbcType=LONGVARCHAR})",
        ")",
        "</foreach>",
        "</script>"
        })
        int insertAll(@Param("records") List<MbgTest> records);
    

    4. 生成方法的名字不太符合我们的风格

    public class CustomIntrospectedTableMyBatis3Ipl extends IntrospectedTableMyBatis3SimpleImpl {
    
        @Override
        protected void calculateXmlAttributes() {
            super.calculateXmlAttributes();
            setSelectByPrimaryKeyStatementId("findById");
            setDeleteByPrimaryKeyStatementId("delete");
            setUpdateByPrimaryKeyStatementId("update");
        }
    }
    

    5. LocalDateTime支持

    public class CustomJavaTypeResolver extends JavaTypeResolverDefaultImpl {
    
        public CustomJavaTypeResolver() {
            super();
            typeMap.put(Types.TIMESTAMP, new JdbcTypeInformation("TIMESTAMP",
                    new FullyQualifiedJavaType(LocalDateTime.class.getName())));
        }
    }
    

    6. 写别的方法的时候不想写select *的话就得把字段给拷贝一遍,不方便维护(表上改了,代码忘记改了或者改漏了)

    自定义pluginAdater,使得把字段给提取一个变量出来,后面自己写方法的时候就可以引用了。
    生成的代码:

    static final String SELECT_ALL_FIELDS = "select id, status, version, created_time, another_status, msg, long_msg from mbg_test ";
    
    

    7. 枚举类的支持

    mybatis提供了自定义的typeHandler。在使用mybatis generator的时候指定typeHandler即可。

    8. 期望可以生成一个resultMap的id,后面使用的时候只需要@ResultMap(value = "id"), 不用再写一遍映射关系

    自定义pluginAdater,在findById方法生成后,修改一下方法的annotation加上id
    扩展后自动生成代码如下

    @Select({
            "select",
            "id, status, version, created_time, another_status, msg, long_msg",
            "from mbg_test",
            "where id = #{id,jdbcType=BIGINT}"
        })
        @Results(value = {
            @Result(column="id", property="id", jdbcType=JdbcType.BIGINT, id=true),
            @Result(column="status", property="status", typeHandler=IntEnumTypeHandler.class, jdbcType=JdbcType.TINYINT),
            @Result(column="version", property="version", jdbcType=JdbcType.BIGINT),
            @Result(column="created_time", property="createdTime", jdbcType=JdbcType.TIMESTAMP),
            @Result(column="another_status", property="anotherStatus", typeHandler=IntEnumTypeHandler.class, jdbcType=JdbcType.TINYINT),
            @Result(column="msg", property="msg", jdbcType=JdbcType.LONGVARCHAR),
            @Result(column="long_msg", property="longMsg", jdbcType=JdbcType.LONGVARCHAR)
        }, id="mbgTest")
        MbgTest findById(Long id);
    

    五.源码简单解读

    1. 最外层,读取<context> 然后挨个处理

    一个context对应一个数据库

    // 读取<context> 然后挨个处理
    contextsToRun = configuration.getContexts();
    
    // methods related to code generation.
    // Methods should be called in this order:
    // 1.获取分析步骤(对于每个context来说有两步,1连接数据库,2分析表)
    context.getIntrospectionSteps();
    // 2.分析需要生成代码的相关表
    context.introspectTables(callback, warnings, fullyQualifiedTableNames);
    // 3.获取生成文件的步骤
    context.getGenerationSteps();
    // 4.获取需要生成的文件
    // 每个context处理好后会把结果放到generatedJavaFiles,generatedXmlFiles,generatedKotlinFiles三个列表中
    context.generateFiles(callback, generatedJavaFiles,
                        generatedXmlFiles, generatedKotlinFiles, warnings);
    
    // 写文件
    for (GeneratedXmlFile gxf : generatedXmlFiles) {
        projects.add(gxf.getTargetProject());
        writeGeneratedXmlFile(gxf, callback);
    }
        
    for (GeneratedJavaFile gjf : generatedJavaFiles) {
        projects.add(gjf.getTargetProject());
        writeGeneratedJavaFile(gjf, callback);
    }
        
    for (GeneratedKotlinFile gkf : generatedKotlinFiles) {
        projects.add(gkf.getTargetProject());
        writeGeneratedKotlinFile(gkf, callback);
    }
    
    

    2. 分析表introspectTables

    // 1.获取类型处理器
    JavaTypeResolver javaTypeResolver = ObjectFactory
                    .createJavaTypeResolver(this, warnings);
    // 2.获取数据库分析器,分析表
    DatabaseIntrospector databaseIntrospector = new DatabaseIntrospector(
                        this, connection.getMetaData(), javaTypeResolver, warnings);
    databaseIntrospector.introspectTables(tc);                    
    
    // 分析字段,包括1. 排除忽略字段,2.获取列的元数据,并根据元数据得出对应的java类型和属性名 3.应用<columnOverride>的设置 4.处理<generatedKey>字段相关
    removeIgnoredColumns(tc, columns);
    calculateExtraColumnInformation(tc, columns);
    applyColumnOverrides(tc, columns);
    calculateIdentityColumns(tc, columns);
    
    // 分析表的元数据,并且得到类名,主键信息等
    List<IntrospectedTable> introspectedTables = calculateIntrospectedTables(
                    tc, columns);
    

    3. 生成文件内容generateFiles(以client生成为例)

    ps:官方提供了PluginAdater,调用Plugin的地方都是我们可以插入自己逻辑的地方

    // 1.获取插件列表,plugin.validate为true的才会加入
            pluginAggregator = new PluginAggregator();
            Plugin plugin = ObjectFactory.createPlugin(this,
                        pluginConfiguration);
                        
            // 2.生成interface
            Interface interfaze = new Interface(type);
            
            // 3. 生成基本方法
            addDeleteByPrimaryKeyMethod(interfaze);
            addInsertMethod(interfaze);
            addSelectByPrimaryKeyMethod(interfaze);
            addSelectAllMethod(interfaze);
            addUpdateByPrimaryKeyMethod(interfaze);
    
            // 4. 调用Plugin的方法(这个时候我们可以在plugin里面往interfaze上面挂任何我们想要的代码)
            if (context.getPlugins().clientGenerated(interfaze, introspectedTable)) {
                answer.add(interfaze);
            }
    
    

    4. 官方提供的methodGenerator

    // 1.生成方法
     Method method = new Method(introspectedTable.getDeleteByPrimaryKeyStatementId());
    
    // 2.annotation
    addMapperAnnotations(method);
    
    // 3.调用plugin对应的方法(我们可以在这里修改这个method,或者阻止这个method生成)
    if (context.getPlugins().clientDeleteByPrimaryKeyMethodGenerated(
                    method, interfaze, introspectedTable)) {
                //  4.添加import
                addExtraImports(interfaze);
                interfaze.addImportedTypes(importedTypes);
                // 5.把这个方法挂到interface上面
                interfaze.addMethod(method);
    }
    

    六.定制mybatis generator的切入点---PluginAdapter

    PluginAdapter提供了丰富的逻辑切入点,这些方法会在mybatis generator的各个环节被触发。而且运行的上下文环境(context),配置(properties)会被注入其中。

    相关文章

      网友评论

          本文标题:定制MyBatis Generator减少mybatis的使用负

          本文链接:https://www.haomeiwen.com/subject/nyictltx.html