mybatis-generator-plugin:mybatis逆向工程插件开发实践

项目地址:https://gitee.com/chuyunfei/mybatis-generator-plugin.git
下面的是教程,上面的是成品,但是需要改东西才能在你的电脑上跑,下面教程有,希望可以帮到你。

一、我为什么要用 mybatis-generator ?

1、我不想写SQL语句

在使用mybatis进行开发的时候,所有的SQL语句是需要我们自己手动去写的,哪怕是:select * from table_name; 这样子简单的语句。这些简单、繁复、枯燥、无聊的工作占据了我们很大的一部分开发时间,所以我不想写这些简单的SQL,想让别人把这些简单的给完成了,我再去写那些关键的SQL语句。

2、数据表结构改变比较频繁

很少有什么项目的需求可以在开始开发前就直接确定完的,难免会在开发的过程中进行需求的更改而导致数据表的更改,而mybatis的映射文件是与结构表高度相关的,如果在大量的映射文件中找到指定的配置难免会出现不必要的错误,所以对于数据表的映射文件的管理最好是自动化的,减少人为造成bug。在我开发的过程中,项目的数据表习惯于写在一个SQL文件里面,变换数据表结构时修改这个文件而不是直接修改数据库,可以保证数据库里面的数据表结构和这个SQL文件里面所呈现的一致。

3、稳定Dao层的接口,避免非必要的对以前的业务代码进行重构

在自己使用mybatis进行开发时,mapper的接口及其xml配置都是个人进行命名的,很难保证数据表结构或者需求更改后仍然保持稳定,特别是在没有一个强制规范的时候,mapper的接口会写的乱七八糟,带有很强烈的个性,这种个性对于非第一代开发人员的程序维护将造成很大的障碍,所以最好可以将Dao层的API接口稳定,便于程序的维护,也可以明确API含义,避免API含糊其辞,比如这个方法名在一个mapper标识的是一个意思,在另外一个mapper里面标识的却是完全风马牛不相及的意思,这会增加理解的成本,增加维护的难度。

4、我懒

就是懒,不想做哪些明明别人都已经给我解决的问题,不想动,躺着不好吗?

二、搭建自己的mybatis-generator工程

最开始我在想到底要不要写这个,毕竟网上一搜一大堆,但是根据我搭建的惨痛教训,我觉得还是写一下,但是事先声明环境,不是这个环境的后面的各个阶段都要找到对应环境的相应解决方案,好了,我的开发环境如下:

  1. JDK8
  2. mybatis:3.4.5
  3. mybatis-generator-core:1.3.2
  4. maven 3 (IDEA插件)
  5. IDEA 企业版
  6. MySQL 5.7 (高版本MySQL这里需要注意配置 jdbc.url 的附带参数的问题,比如什么useSSL=false,什么时区,什么字符编码什么的)
  7. mybatis-generator-maven-plugin:1.3.2

1、搭建一个maven项目

记得改maven的三座标,后面关于maven的座标的信息记得换成你自己的座标

搭建完成了的样子是这个样子的:
基本项目结构
然后添加一下项目依赖:

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cqupt.mislab</groupId>
    <artifactId>generator</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator-core -->
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.3.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.2</version>
                <configuration>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

依赖添加后会出现一个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>

    <!--这个需要配置一个jar包的位置,一般在maven长裤里面就直接找到了-->
    <classPathEntry location="C:\Users\chuyunfei\.m2\repository\mysql\mysql-connector-java\5.1.6\mysql-connector-java-5.1.6.jar"/>

    <!--生成Example类-->
    <context id="mysql" defaultModelType="flat" targetRuntime="MyBatis3">

        <!-- 生成的Java文件的编码 -->
        <property name="javaFileEncoding" value="UTF-8"/>
        <!-- 格式化java代码 -->
        <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
        <!-- 格式化XML代码 -->
        <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>

        <!--MySQL风格的数据库分割符-->
        <property name="autoDelimitKeywords" value="true"/>
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        <!-- 数据库连接 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/erpm"
                        userId="root" password="root" />

        <!--类处理器-->
        <javaTypeResolver type="org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl">
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>

        <!-- java模型生成配置 -->
        <javaModelGenerator targetPackage="edu.cqupt.mislab.erpm.user.model.entity" targetProject="src/main/java">
            <property name="enableSubPackages" value="false"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>

        <!--xml生成配置-->
        <sqlMapGenerator targetPackage="mybatis.mapper.user" targetProject="src/main/resources">
            <property name="enableSubPackages" value="false"/>
        </sqlMapGenerator>

        <!--接口生成配置-->
        <javaClientGenerator targetPackage="edu.cqupt.mislab.erpm.user.dao" targetProject="src/main/java/"
                             type="XMLMAPPER">
            <property name="enableSubPackages" value="false"/>
        </javaClientGenerator>

        <!-- 数据库表:% 表示所有的表名 -->
        <table schema="" tableName="%"
               enableInsert="true"
               enableDeleteByPrimaryKey="true" enableSelectByPrimaryKey="true" enableUpdateByPrimaryKey="true"
               delimitIdentifiers="true"
               enableCountByExample="false" enableSelectByExample="false" enableDeleteByExample="false"
               enableUpdateByExample="false" selectByExampleQueryId="false" selectByPrimaryKeyQueryId="false">
            <generatedKey column="id" sqlStatement="MySql" type="post" identity="true"/>
        </table>
    </context>
</generatorConfiguration>

检验成果的时候到了:
插件运行示意
看见我们的成果了吗?
运行结果

当然前提是你正确配置了相关信息,在这里有一篇我不知道哪里偷来的详细解释,在此感谢那位匿名的仁兄!(后来我找到了,在这里:https://www.cnblogs.com/xiaocao1434/p/8797636.html ,感谢 小草1434!)

配置文件完整的解释:generatorConfigComment.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>
    <!--参考资料:https://www.cnblogs.com/xiaocao1434/p/8797636.html-->

    <!--加载mybatis逆向工程需要的jar包位置,比如说需要数据库驱动包,需要指定一个驱动包的完整路径(必须)-->
    <classPathEntry location="C:\Users\chuyunfei\.m2\repository\mysql\mysql-connector-java\5.1.6\mysql-connector-java-5.1.6.jar"/>

    <!--
        context:生成一组对象的环境
            id:必选,上下文id,用于在生成错误时提示
            defaultModelType:指定生成对象的样式
                1,conditional:类似hierarchical;
                2,flat:所有内容(主键,blob)等全部生成在一个对象中;
                3,hierarchical:主键生成一个XXKey对象(key class),Blob等单独生成一个对象,其他简单属性在一个对象中(record class)
            targetRuntime:
                1,MyBatis3:默认的值,生成基于MyBatis3.x以上版本的内容,包括XXXBySample;
                2,MyBatis3Simple:类似MyBatis3,只是不生成XXXBySample;
            introspectedColumnImpl:类全限定名,用于扩展MBG
    -->
    <context id="mysql" defaultModelType="hierarchical" targetRuntime="MyBatis3Simple">

        <!--
            自动识别数据库关键字,默认false。
            设置为true时,根据SqlReservedWords中定义的关键字列表。
            一般保留默认值,遇到数据库关键字(Java关键字),使用columnOverride覆盖
         -->
        <property name="autoDelimitKeywords" value="false"/>

        <!-- 生成的Java文件的编码 -->
        <property name="javaFileEncoding" value="UTF-8"/>

        <!-- 格式化java代码 -->
        <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>

        <!-- 格式化XML代码 -->
        <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>

        <!--
            beginningDelimiter和endingDelimiter:指明数据库的用于标记数据库对象名的符号。
            比如:ORACLE就是双引号,MYSQL默认是`反引号;
        -->
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        <!--配置数据库的连接:必须有!!-->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql:///service_system"
                        userId="root" password="root">
            <!--设置Driver的属性,里面的设置是Driver的属性,并不是连接属性-->
            <!--<property name="" value=""/>-->
        </jdbcConnection>

        <!--
            java类型处理器
                用于处理DB中的类型到Java中的类型,默认使用JavaTypeResolverDefaultImpl;
                注意:默认会先尝试使用Integer,Long,Short等来对应DECIMAL和 NUMERIC数据类型;
        -->
        <javaTypeResolver type="org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl">
            <!--
                true:使用BigDecimal对应DECIMAL和 NUMERIC数据类型
                false:默认,
                    scale>0;length>18:使用BigDecimal;
                    scale=0;length[10,18]:使用Long;
                    scale=0;length[5,9]:使用Integer;
                    scale=0;length<5:使用Short;
             -->
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>


        <!--
            java模型创建器,是必须要的元素,负责:
                1,key类(见context的defaultModelType);
                2,java类;
                3,查询类
            targetPackage:生成的类要放的包,真实的包受enableSubPackages属性控制;
            targetProject:目标项目,指定一个存在的目录下,生成的内容会放到指定目录中,如果目录不存在,MBG不会自动建目录
         -->
        <javaModelGenerator targetPackage="com.copm.model" targetProject="src/main/java">

            <!--自动为每一个生成的类创建一个构造方法,构造方法包含了所有的field;而不是使用setter-->
            <property name="constructorBased" value="false"/>

            <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false -->
            <property name="enableSubPackages" value="true"/>

            <!--
                是否创建一个不可变的类。
                如果为true,那么MBG会创建一个没有setter方法的类,取而代之的是类似constructorBased的类
             -->
            <property name="immutable" value="false"/>

            <!--
                设置一个根对象。
                如果设置了这个根对象,那么生成的keyClass或者recordClass会继承这个类;在Table的rootClass属性中可以覆盖该选项
                注意:如果在key class或者record class中有root class相同的属性,MBG就不会重新生成这些属性了,包括:
                    1,属性名相同,类型相同,有相同的getter/setter方法;
             -->
            <!--<property name="rootClass" value="com.copm.domain"/>-->

            <!-- 设置是否在getter方法中,对String类型字段调用trim()方法 -->
            <property name="trimStrings" value="false"/>
        </javaModelGenerator>


        <!--
            生成SQL map的XML文件生成器。
            注意:在Mybatis3之后,我们可以使用mapper.xml文件+Mapper接口(或者不用mapper接口),或者只使用Mapper接口+Annotation。
                所以,如果 javaClientGenerator配置中配置了需要生成XML的话,这个元素就必须配置
            targetPackage/targetProject:同javaModelGenerator
         -->
        <sqlMapGenerator targetPackage="com.copm.xml" targetProject="src/main/resources">
            <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false -->
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>


        <!--
            对于mybatis来说,即生成Mapper接口。
            注意:如果没有配置该元素,那么默认不会生成Mapper接口
            targetPackage/targetProject:同javaModelGenerator
            type:选择怎么生成mapper接口(在MyBatis3/MyBatis3Simple下):
                1,ANNOTATEDMAPPER:会生成使用Mapper接口+Annotation的方式创建(SQL生成在annotation中),不会生成对应的XML;
                2,MIXEDMAPPER:使用混合配置,会生成Mapper接口,并适当添加合适的Annotation,但是XML会生成在XML中;
                3,XMLMAPPER:会生成Mapper接口,接口完全依赖XML;
            注意,如果context是MyBatis3Simple:只支持ANNOTATEDMAPPER和XMLMAPPER
        -->
        <javaClientGenerator targetPackage="com.copm.mapper" type="XMLMAPPER" targetProject="src/main/java">
            <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false -->
            <property name="enableSubPackages" value="true"/>

            <!-- 可以为所有生成的接口添加一个父接口,但是MBG只负责生成,不负责检查-->
            <!--<property name="rootInterface" value=""/>-->

        </javaClientGenerator>

        <!--
            选择一个table来生成相关文件,可以有一个或多个table,必须要有table元素:多个table标签生成多个对应产物
            选择的table会生成以下文件:
                1,SQL map文件
                2,生成一个主键类;
                3,除了BLOB和主键的其他字段的类;
                4,包含BLOB的类;
                5,一个用户生成动态查询的条件类(selectByExample, deleteByExample),可选;
                6,Mapper接口(可选)

            tableName(必要):要生成对象的表名;
            注意:大小写敏感问题。正常情况下,MBG会自动的去识别数据库标识符的大小写敏感度,在一般情况下,MBG会
                根据设置的schema,catalog或tablename去查询数据表,按照下面的流程:
                1,如果schema,catalog或tablename中有空格,那么设置的是什么格式,就精确的使用指定的大小写格式去查询;
                2,否则,如果数据库的标识符使用大写的,那么MBG自动把表名变成大写再查找;
                3,否则,如果数据库的标识符使用小写的,那么MBG自动把表名变成小写再查找;
                4,否则,使用指定的大小写格式查询;
            另外:如果在创建表的时候,使用的""把数据库对象规定大小写,就算数据库标识符是使用的大写,在这种情况下也会使用给定的大小写来创建表名;
                这个时候,请设置delimitIdentifiers="true"即可保留大小写格式;

            可选:
                1,schema:数据库的schema;
                2,catalog:数据库的catalog;
                3,alias:为数据表设置的别名,如果设置了alias,那么生成的所有的SELECT SQL语句中,列名会变成:alias_actualColumnName
                4,domainObjectName:生成的domain类的名字,如果不设置,直接使用表名作为domain类的名字;可以设置为somepck.domainName,那么会自动把domainName类再放到somepck包里面;
                5,enableInsert(默认true):指定是否生成insert语句;
                6,enableSelectByPrimaryKey(默认true):指定是否生成按照主键查询对象的语句(就是getById或get);
                7,enableSelectByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询语句;
                8,enableUpdateByPrimaryKey(默认true):指定是否生成按照主键修改对象的语句(即update);
                9,enableDeleteByPrimaryKey(默认true):指定是否生成按照主键删除对象的语句(即delete);
                10,enableDeleteByExample(默认true):MyBatis3Simple为false,指定是否生成动态删除语句;
                11,enableCountByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询总条数语句(用于分页的总条数查询);
                12,enableUpdateByExample(默认true):MyBatis3Simple为false,指定是否生成动态修改语句(只修改对象中不为空的属性);
                13,modelType:参考context元素的defaultModelType,相当于覆盖;
                14,delimitIdentifiers:参考tableName的解释,注意,默认的delimitIdentifiers是双引号,如果类似MYSQL这样的数据库,使用的是`(反引号,那么还需要设置context的beginningDelimiter和endingDelimiter属性)
                15,delimitAllColumns:设置是否所有生成的SQL中的列名都使用标识符引起来。默认为false,delimitIdentifiers参考context的属性

            注意,table里面很多参数都是对javaModelGenerator,context等元素的默认属性的一个复写;
         -->
        <table tableName="userinfo" >

            <!-- 参考 javaModelGenerator 的 constructorBased属性-->
            <property name="constructorBased" value="false"/>

            <!-- 默认为false,如果设置为true,在生成的SQL中,table名字不会加上catalog或schema; -->
            <property name="ignoreQualifiersAtRuntime" value="false"/>

            <!-- 参考 javaModelGenerator 的 immutable 属性 -->
            <property name="immutable" value="false"/>

            <!-- 指定是否只生成domain类,如果设置为true,只生成domain类,如果还配置了sqlMapGenerator,那么在mapper XML文件中,只生成resultMap元素 -->
            <property name="modelOnly" value="false"/>

            <!-- 参考 javaModelGenerator 的 rootClass 属性
            <property name="rootClass" value=""/>
             -->

            <!-- 参考javaClientGenerator 的  rootInterface 属性
            <property name="rootInterface" value=""/>
            -->

            <!-- 如果设置了runtimeCatalog,那么在生成的SQL中,使用该指定的catalog,而不是table元素上的catalog
            <property name="runtimeCatalog" value=""/>
            -->

            <!-- 如果设置了runtimeSchema,那么在生成的SQL中,使用该指定的schema,而不是table元素上的schema
            <property name="runtimeSchema" value=""/>
            -->

            <!-- 如果设置了runtimeTableName,那么在生成的SQL中,使用该指定的tablename,而不是table元素上的tablename
            <property name="runtimeTableName" value=""/>
            -->

            <!--
                注意,该属性只针对MyBatis3Simple有用;
                如果选择的runtime是MyBatis3Simple,那么会生成一个SelectAll方法,如果指定了selectAllOrderByClause,那么会在该SQL中添加指定的这个order条件;
             -->
            <!--<property name="selectAllOrderByClause" value="age desc,username asc"/>-->

            <!-- 如果设置为true,生成的model类会直接使用column本身的名字,而不会再使用驼峰命名方法,比如BORN_DATE,生成的属性名字就是BORN_DATE,而不会是bornDate -->
            <property name="useActualColumnNames" value="false"/>

            <!-- generatedKey用于生成生成主键的方法,
                如果设置了该元素,MBG会在生成的**<insert>**元素中生成一条正确的<selectKey>元素,该元素可选
                column:主键的列名;
                sqlStatement:要生成的selectKey语句,有以下可选项:
                    Cloudscape:相当于selectKey的SQL为: VALUES IDENTITY_VAL_LOCAL()
                    DB2       :相当于selectKey的SQL为: VALUES IDENTITY_VAL_LOCAL()
                    DB2_MF    :相当于selectKey的SQL为:SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1
                    Derby      :相当于selectKey的SQL为:VALUES IDENTITY_VAL_LOCAL()
                    HSQLDB      :相当于selectKey的SQL为:CALL IDENTITY()
                    Informix  :相当于selectKey的SQL为:select dbinfo('sqlca.sqlerrd1') from systables where tabid=1
                    MySql      :相当于selectKey的SQL为:SELECT LAST_INSERT_ID()
                    SqlServer :相当于selectKey的SQL为:SELECT SCOPE_IDENTITY()
                    SYBASE      :相当于selectKey的SQL为:SELECT @@IDENTITY
                    JDBC      :相当于在生成的insert元素上添加useGeneratedKeys="true"和keyProperty属性
            <generatedKey column="" sqlStatement=""/>
             -->

            <!--
                该元素会在根据表中列名计算对象属性名之前先重命名列名,非常适合用于表中的列都有公用的前缀字符串的时候,
                比如列名为:CUST_ID,CUST_NAME,CUST_EMAIL,CUST_ADDRESS等;
                那么就可以设置searchString为"^CUST_",并使用空白替换,那么生成的Customer对象中的属性名称就不是
                custId,custName等,而是先被替换为ID,NAME,EMAIL,然后变成属性:id,name,email;

                注意,MBG是使用java.util.regex.Matcher.replaceAll来替换searchString和replaceString的,
                如果使用了columnOverride元素,该属性无效;

            <columnRenamingRule searchString="" replaceString=""/>
             -->

            <!-- 用来修改表中某个列的属性,MBG会使用修改后的列来生成domain的属性;
                column:要重新设置的列名;
                注意,一个table元素中可以有多个columnOverride元素哈~
             -->
            <columnOverride column="username">

                <!-- 使用property属性来指定列要生成的属性名称 -->
                <property name="property" value="userName"/>

                <!-- javaType用于指定生成的domain的属性类型,使用类型的全限定名
                <property name="javaType" value=""/>
                 -->

                <!-- jdbcType用于指定该列的JDBC类型
                <property name="jdbcType" value=""/>
                 -->

                <!-- typeHandler 用于指定该列使用到的TypeHandler,如果要指定,配置类型处理器的全限定名
                    注意,mybatis中,不会生成到mybatis-config.xml中的typeHandler
                    只会生成类似:where id = #{id,jdbcType=BIGINT,typeHandler=com._520it.mybatis.MyTypeHandler}的参数描述
                <property name="jdbcType" value=""/>
                -->

                <!-- 参考table元素的delimitAllColumns配置,默认为false
                <property name="delimitedColumnName" value=""/>
                 -->
            </columnOverride>

            <!-- ignoreColumn设置一个MGB忽略的列,如果设置了改列,那么在生成的domain中,生成的SQL中,都不会有该列出现
                column:指定要忽略的列的名字;
                delimitedColumnName:参考table元素的delimitAllColumns配置,默认为false

                注意,一个table元素中可以有多个ignoreColumn元素
            <ignoreColumn column="deptId" delimitedColumnName=""/>
            -->
        </table>

    </context>

</generatorConfiguration>

三、逆向工程插件开发——重点

为什么纠结这个插件开发,当然不是头脑发热什么追求技术什么的,当然是开发了插件可以让我更舒服、更快、更灵活、更有效的开发。

1、为什么我要来写这个逆向工程插件开发博客

每一次我搜索 mybatis-generator 插件开发 的时候,出来的都是教我怎么用mybatis-generator的,对,没错,以插件的方式使用mybatis-generator,也就是我们到目前为止已经实现的部分,嗯…,每次我都想换Google再来搜一次。

2、初探逆向工程插件

没办法,查不到资料只有自己动手丰衣足食了,想到mybatis的插件开发,我试着在刚才的项目中 double shift and input plugin !!!!一个大大的惊喜:
在这里插入图片描述
秉承着打破砂锅问到底的小强精神,我点了进去,于是彻底对其宣判死刑:
接口类层次图
没错,真相只有一个,就是这个关键类。
到目前为止我发现自己其实是走了弯路的,回想起配置文件里面是不是有可以配置插件的配置?还说不是有默认的几个插件嘛?不就是我瞎猫碰到死耗子的那几个插件吗?然后顺藤摸瓜,依然可以找到这个关键接口。

3、准备依葫芦画瓢开发插件

先选一个插件来进行研究,就拿带toString那个插件嘛,就决定是它了:

在这里插入图片描述
继续了解这个方法:

    private void generateToString(IntrospectedTable introspectedTable,
            TopLevelClass topLevelClass) {
        //创建了一个方法,叫toString: method.setName("toString"); //$NON-NLS-1$
        Method method = new Method();
        //public访问权限
        method.setVisibility(JavaVisibility.PUBLIC);
        //返回值为String
        method.setReturnType(FullyQualifiedJavaType.getStringInstance());
        method.setName("toString"); //$NON-NLS-1$
        //如果支持Java5就加一个注解@Override
        if (introspectedTable.isJava5Targeted()) {
            method.addAnnotation("@Override"); //$NON-NLS-1$
        }

        context.getCommentGenerator().addGeneralMethodComment(method,
                introspectedTable);

        //为方法添加一行代码体
        method.addBodyLine("StringBuilder sb = new StringBuilder();"); //$NON-NLS-1$
        method.addBodyLine("sb.append(getClass().getSimpleName());"); //$NON-NLS-1$
        method.addBodyLine("sb.append(\" [\");"); //$NON-NLS-1$
        method.addBodyLine("sb.append(\"Hash = \").append(hashCode());"); //$NON-NLS-1$
        StringBuilder sb = new StringBuilder();
        //将类里面的字段都给拼凑起来
        for (Field field : topLevelClass.getFields()) {
            String property = field.getName();
            sb.setLength(0);
            sb.append("sb.append(\"").append(", ").append(property) //$NON-NLS-1$ //$NON-NLS-2$
                    .append("=\")").append(".append(").append(property) //$NON-NLS-1$ //$NON-NLS-2$
                    .append(");"); //$NON-NLS-1$
            method.addBodyLine(sb.toString());
        }

        method.addBodyLine("sb.append(\"]\");"); //$NON-NLS-1$
        method.addBodyLine("return sb.toString();"); //$NON-NLS-1$
		//给这个类添加这个toString方法
        topLevelClass.addMethod(method);
    }

en…,感觉这个也太…简单了吧?咋这么简单呢?还以为要代理要探针要什么高深莫测的技术,结果我裤子都脱了,给我看这个!!!
其实仔细、冷静、慢慢的分析还是有道理的,逆向工程生成文件其实就是根据数据库和配置文件里面的元数据,生成对需要生成的文件的元数据描述,最后才会通过这些元数据描述来进行文件的生成。也就是说我们现在修改的其实只是这个类的描述数据而已,也就解释了为什么这么简单的原因,因为到插件这里压根就还没有生成文件!!

4、继续深入的发散思考

  1. 如果对方法都可以进行描述,那么字段、接口、参数、返回值、注解、注释这些应该也是通过一定的数据结构来进行描述的,嗯,一定是。
  2. 如果我操作的数据仅仅是对于文件的描述数据,那么我将可以控制整个要被生成出来的类的一切东西,因为这个类的一切东西应该都是通过描述数据来描述的,简直可以为所欲为啊,颠倒黑白什么的简直不要太容易。
  3. 既然开放了插件接口,那么肯定是可以影响其底层逻辑的,也就是说我们应该可以通过插件来影响model、mapper、xml三个模块的行为。

5、对插件思考的验证

先查看插件接口里面有哪些方法:
在这里插入图片描述
仔细分析上面的接口和查看接口注释可以发现接口的方法被分为五个部分:

  1. 以init*、set*、context*开头的方法,注释多为设置基本信息,也就是为插件设置元数据的,就是配置在配置文件里面插件的配置,可以用来支持插件行为的配置化。同时还有一些上下文信息,多数都是只调用一遍。
  2. 以client*开头的方法,注释发现都是对mapper接口的插件方法接口。
  3. 以model*开头的方法,注释多是对model实体类的插件方法接口。
  4. 以sqlMap*开头的方法,注释多是对xml配置文件的插件方法接口。
  5. 以provider*开头的方法,注释多是对于SQL provider数据库厂商的插件方法接口。

看着这些接口的名字再看看配置文件的结构:
在这里插入图片描述
是不是突然恍然大悟!!!原来一切都是安排。

6、开始进行插件开发

特别注意:虽然我很想把我知道的都给你说,都是事实是根本就没有办法说清楚,但是如果你按照下面的步骤做,你将能够为你的应用开发无论是xml、mapper、model那个方面的。

  1. 大胆猜测,信心求证
  2. 多看源码,有一点点想法就去看看源码再去实践。
  3. 不要怂。

①、开发准备

由于是以插件的方式运行,所以需要把自己开发的插件加入插件依赖,更改pom文件,将本项目添加到插件依赖:

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.2</version>
                <configuration>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                </configuration>
                <dependencies>
                    <dependency>
                        <!--这个座标记得改成你自己的,由于是仓库依赖,需要将本项目安装到本地仓库-->
                        <groupId>cqupt.mislab</groupId>
                        <artifactId>generator</artifactId>
                        <version>1.0-SNAPSHOT</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

为了方便测试,写一个maven命令:

clean:clean
compiler:compile 
jar:jar 
install:install-file -Dfile=F:\JAVA\Projects\Main\generator\target\generator-1.0-SNAPSHOT.jar 
-DgroupId=cqupt.mislab 
-DartifactId=generator 
-Dversion=1.0-SNAPSHOT 
-Dpackaging=jar 
mybatis-generator:generate

注释:

  1. 清空当前项目的编译结果
  2. 编译当前项目
  3. 打包当前项目
  4. 将当前项目的jar包安装到本地仓库:打包后jar包的绝对路劲,jar包的三座标,安装的是jar包
  5. 调用mybatis-generator插件进行工程生成

注意更改你的jar包位置和maven三座标,然后将这个命令搞到这里来:
在这里插入图片描述
记得点右下角的apply,然后将看见这个:
在这里插入图片描述
现在点击右边那个绿色三角形执行一下,可以看见和刚刚一样的结果就是成功了。

②、第一个插件:为mapper添加@Repository注解

根据我们前面的分析和自带插件的分析,首先一个插件需要事先Plugin接口,当然为了更方便都是直接继承PluginAdapter,然后由于我们是想写修改Mapper接口行为的插件(为mapper填加注解),所以挑选以client*开头的方法,最终得到:

package plugins;

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.TopLevelClass;

import java.util.List;

public class MapperAnnotationPlugin extends PluginAdapter {

    @Override
    public boolean clientGenerated(Interface interfaze,TopLevelClass topLevelClass,IntrospectedTable introspectedTable){

        //首先要导入这个类型撒,addImportedType,生动形象,还有一些什么:addMethod、addAnnotation什么的,见名知意
        interfaze.addImportedType(new FullyQualifiedJavaType("org.springframework.stereotype.Repository"));

        //给它加一顶注解
        interfaze.addAnnotation("@Repository");

        return true;
    }

    @Override//每个插件必须实现,具体的就是插件执行中的警告信息,该插件的配置信息是否完备等
    public boolean validate(List<String> warnings){

        return true;
    }
}

再把我们的插件配置进去:
在这里插入图片描述
执行,点击配置的那个maven命令:
在这里插入图片描述
现在我们要让这个插件更加智能,我可以让他在某些表里面生效,某些表不失效,可以添加多个注解,撸起袖子加油干:
首先为插件配置属性:

        <plugin type="plugins.MapperAnnotationPlugin">
            <!--全局的插件开关,true的时候启用-->
            <property name="globalEnable" value="true"/>
            <!--多个需要加的注解之间的分隔符-->
            <property name="globalAnnotationSeparator" value=","/>
            <!--需要加的注解,如果多个就用分隔符分开-->
            <property name="globalAnnotationClasses" value="org.springframework.stereotype.Repository"/>
        </plugin>

其次为表配置是否启用插件:

        <table schema="" tableName="user_student_basic_info"
               enableInsert="true"
               enableDeleteByPrimaryKey="true" enableSelectByPrimaryKey="true" enableUpdateByPrimaryKey="true"
               delimitIdentifiers="true"
               enableCountByExample="false" enableSelectByExample="false" enableDeleteByExample="false"
               enableUpdateByExample="false" selectByExampleQueryId="false" selectByPrimaryKeyQueryId="false">
            <!--这张表是否开启这个插件-->
            <property name="enableMapperAnnotation" value="true"/>
            <!--这张表的分隔符,优先级比全局的高-->
            <property name="annotationSeparator" value=","/>
            <!--除了全局需要加的注解外这个mapper额外需要加的注解-->
            <property name="annotationClasses" value="org.springframework.stereotype.Repository"/>
            <generatedKey column="id" sqlStatement="MySql" type="post" identity="true"/>
        </table>

现在去改造我们原来的简单的插件:

package plugins;

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.TopLevelClass;

import java.util.List;
import java.util.Properties;

/**
 * 0、全局在插件配置属性里面使用:globalEnable = true 开启
 * 1、实现为Mapper文件添加指定注解的功能
 * 2、单表开启参数:enableMapperAnnotation = true
 * 3、单表注解的分隔符,配置参数为:annotationSeparator,默认为:,
 * 4、单表注解类,使用配置:annotationClasses
 * 5、全局分隔符使用:globalAnnotationSeparator 配置
 * 6、全局注解使用:globalAnnotationClasses 配置
 */
public class MapperAnnotationPlugin extends PluginAdapter {

    @Override
    public boolean clientGenerated(Interface interfaze,TopLevelClass topLevelClass,IntrospectedTable introspectedTable){

        //判断插件是否启用
        if(!isPluginEnable(introspectedTable)){
            return true;
        }

        final Properties tableProperties = getTableProperties(introspectedTable);

        //获取表里面配置的分隔符,优先级比全局的高,默认为:,
        String separator = tableProperties.getProperty("annotationSeparator");

        if(separator == null){

            //获取全局分隔符
            separator = properties.getProperty("globalAnnotationSeparator",",");
        }

        String annotationClass = null;

        //获取这张表需要添加的注解
        String tableAnnotationClass = tableProperties.getProperty("annotationClasses");

        if(tableAnnotationClass!=null){
            annotationClass = tableAnnotationClass;
        }

        //全局需要配置注解
        String globalAnnotationClass = properties.getProperty("globalAnnotationClasses");

        if(globalAnnotationClass != null){

            if(tableAnnotationClass == null){

                annotationClass = globalAnnotationClass;
            }else {

                annotationClass = tableAnnotationClass + separator + globalAnnotationClass;
            }
        }

        if(annotationClass != null){

            String[] annotationClazz = annotationClass.split(separator);

            if(annotationClazz.length > 0){

                for(String clazz : annotationClazz){

                    //导入注解
                    interfaze.addImportedType(new FullyQualifiedJavaType(clazz));
                    //添加注解
                    interfaze.addAnnotation("@" + clazz.substring(clazz.lastIndexOf(".")+1));
                }
            }
        }

        return true;
    }

    @Override
    public boolean validate(List<String> warnings){

        return true;
    }

    /**
     * 获取表的配置数据
     */
    private Properties getTableProperties(IntrospectedTable introspectedTable){

        return introspectedTable.getTableConfiguration().getProperties();
    }

    /**
     * 判断一个表是否开启这个插件
     */
    private boolean isPluginEnable(IntrospectedTable introspectedTable){

        //获取配置在插件里面的属性,全局开关
        final String globalEnable = properties.getProperty("globalEnable");

        //如果没有1配置,默认为全局开启
        if(globalEnable == null || Boolean.valueOf(globalEnable)){

            //获取配置在表里面的属性
            final String tableEnable = introspectedTable.getTableConfigurationProperty("enableMapperAnnotation");

            if(tableEnable != null){

                return Boolean.valueOf(tableEnable);
            }

            return true;
        }

        return false;
    }
}

注意重新测试的时候把你上一次测试的生成文件删除掉

运行maven命令:
在这里插入图片描述
然后去测试开关这个插件吧。

③、开发自己的注释生成器

在上一张图片中你一定看见了那些令人感到厌烦的自带的注释,现在继续自定义的注释生成器,也是最常用的,可以将SQL里面的comment自动的生成到model的字段上面去,但是这个有专门的方式,但是可以思考怎么样用插件实现,现在来进行常规的方式:自定义注释生成器。

package plugins;

import org.mybatis.generator.api.*;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.config.MergeConstants;
import org.mybatis.generator.config.PropertyRegistry;

import java.io.Serializable;
import java.util.Date;
import java.util.Properties;

import static org.mybatis.generator.internal.util.StringUtility.isTrue;

/**
 * 1、将SQL的comment转移到实体类里面
 */
public class ConsumerCommentGenerator implements CommentGenerator , Serializable {

    private Properties properties;

    private boolean suppressDate;

    private boolean suppressAllComments;

    public ConsumerCommentGenerator() {
        super();
        properties = new Properties();
        suppressDate = false;
        suppressAllComments = false;
    }

    public void addJavaFileComment(CompilationUnit compilationUnit) {
        return;
    }

    /**
     * Adds a suitable comment to warn users that the element was generated, and
     * when it was generated.
     */
    public void addComment(XmlElement xmlElement) {
        if (suppressAllComments) {
            return;
        }
        xmlElement.addElement(new TextElement("<!--切勿修改-->"));
    }

    public void addRootComment(XmlElement rootElement) {

        rootElement.addElement(new TextElement(
                "<!--自定义SQL与逆向工程SQL分界线:上方为自定义SQL区-->"));
    }

    @Override
    public void addConfigurationProperties(Properties properties) {
        this.properties.putAll(properties);

        suppressDate = isTrue(properties
                .getProperty(PropertyRegistry.COMMENT_GENERATOR_SUPPRESS_DATE));

        suppressAllComments = isTrue(properties
                .getProperty(PropertyRegistry.COMMENT_GENERATOR_SUPPRESS_ALL_COMMENTS));
    }

    /**
     * This method adds the custom javadoc tag for. You may do nothing if you do
     * not wish to include the Javadoc tag - however, if you do not include the
     * Javadoc tag then the Java merge capability of the eclipse plugin will
     * break.
     *
     * @param javaElement
     *            the java element
     */
    protected void addJavadocTag(JavaElement javaElement,boolean markAsDoNotDelete) {
        javaElement.addJavaDocLine(" *");
        StringBuilder sb = new StringBuilder();
        sb.append(" * ");
        sb.append(MergeConstants.NEW_ELEMENT_TAG);
        if (markAsDoNotDelete) {
            sb.append(" do_not_delete_during_merge");
        }
        String s = getDateString();
        if (s != null) {
            sb.append(' ');
            sb.append(s);
        }
        javaElement.addJavaDocLine(sb.toString());
    }

    /**
     * This method returns a formated date string to include in the Javadoc tag
     * and XML comments. You may return null if you do not want the date in
     * these documentation elements.
     *
     * @return a string representing the current timestamp, or null
     */
    protected String getDateString() {
        if (suppressDate) {
            return null;
        } else {
            return new Date().toString();
        }
    }

    public void addClassComment(InnerClass innerClass,IntrospectedTable introspectedTable) {
        if (suppressAllComments) {
            return;
        }

        StringBuilder sb = new StringBuilder();

        innerClass.addJavaDocLine("/**");
        innerClass.addJavaDocLine(" * This class was generated by MyBatis Generator."); //$NON-NLS-1$

        sb.append(" * This class corresponds to the database table "); //$NON-NLS-1$
        sb.append(introspectedTable.getFullyQualifiedTable());
        innerClass.addJavaDocLine(sb.toString());

        addJavadocTag(innerClass, false);

        innerClass.addJavaDocLine(" */"); //$NON-NLS-1$
    }

    public void addEnumComment(InnerEnum innerEnum,
                               IntrospectedTable introspectedTable) {
        return;
    }

    public void addFieldComment(Field field,
                                IntrospectedTable introspectedTable,
                                IntrospectedColumn introspectedColumn) {
        if (suppressAllComments) {
            return;
        }

        field.addJavaDocLine("/**");

        String remarkLine = introspectedColumn.getRemarks();

        if(remarkLine != null){

            String[] remarks = remarkLine.split(System.getProperty("line.separator"));

            for(String remark : remarks){

                field.addJavaDocLine(" * " + remark);
            }
        }
        field.addJavaDocLine("*/");
    }

    public void addFieldComment(Field field, IntrospectedTable introspectedTable) {
        if (suppressAllComments) {
            return;
        }

        field.addJavaDocLine("/**");
        field.addJavaDocLine(" * 类静态字段");
        field.addJavaDocLine(" */");
    }

    public void addGeneralMethodComment(Method method,IntrospectedTable introspectedTable) {
        if (suppressAllComments) {
            return;
        }

        method.addJavaDocLine("/**");
        method.addJavaDocLine(" * 切勿修改");
        method.addJavaDocLine(" */");
    }

    public void addGetterComment(Method method,IntrospectedTable introspectedTable,IntrospectedColumn introspectedColumn) {
        if (suppressAllComments) {
            return;
        }

        method.addJavaDocLine("/**");
        method.addJavaDocLine(" * 切勿修改");
        method.addJavaDocLine(" */");
    }

    public void addSetterComment(Method method,
                                 IntrospectedTable introspectedTable,
                                 IntrospectedColumn introspectedColumn) {
        if (suppressAllComments) {
            return;
        }

        method.addJavaDocLine("/**");
        method.addJavaDocLine(" * 切勿修改");
        method.addJavaDocLine(" */");
    }

    public void addClassComment(InnerClass innerClass,
                                IntrospectedTable introspectedTable, boolean markAsDoNotDelete) {
        if (suppressAllComments) {
            return;
        }

        innerClass.addJavaDocLine("/**");
        innerClass.addJavaDocLine(" * 切勿修改");
        innerClass.addJavaDocLine(" * 数据表:" + introspectedTable.getFullyQualifiedTable());
        innerClass.addJavaDocLine(" */");
    }
}

现在来配置这个自定义的注释生成器:

在这里插入图片描述
运行maven命令:
在这里插入图片描述

四、压轴大戏——为JPA服务的mybatis-generator

1、为什么要为JPA开发mybatis-generator插件

这个是我当前项目的问题,我持久层采用JPA,但是IDEA自带的JPA工具无法生成字段注释,而且定数据表也是SQL,虽然最后都需要转换为JPA的ERP模型,但是自己写Model和注释简直要把我逼疯,所以想到了逆向工程插件来实现这个问题,具体的转换思路是:

  1. 定数据表,写成SQL文件
  2. 将SQL文件执行到数据库
  3. 使用mybatis逆向工程将其转换为model
  4. 使用mybatis逆向工程插件将model转换为JPA的Entity
  5. 少量修改Entity将数据模型转换为对象模型

按照这个思路可以解决将数据表转换为model所花的大量时间(因为数据表多的令人绝望),还可以把数据表上面的注释也给转换过来,一举多得。
在此之间,依赖 lombok 来保持Model的类定义的清爽。

2、开发JPA的Entity插件

由于我们修改的是model,所以选择覆盖model*的接口方法

package plugins;

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.Field;
import org.mybatis.generator.api.dom.java.TopLevelClass;

import java.util.List;

public class JpaEntityPlugin extends PluginAdapter {

    /**
     * 为JpaEntity的类级别增加注解的方法
     */
    @Override
    public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass,IntrospectedTable introspectedTable){

        //清空里面所有的方法,所有的方法由lombok自动生成,保持文件的干爽和清晰
        topLevelClass.getMethods().clear();

        //导入需要导入的类
        topLevelClass.addImportedType("lombok.*");
        topLevelClass.addImportedType("javax.persistence.*");

        final List<String> annotations = topLevelClass.getAnnotations();

        //添加lombok的相关注解
        annotations.add("@Data");
        annotations.add("@Builder");
        annotations.add("@NoArgsConstructor");
        annotations.add("@AllArgsConstructor");

        //添加JPA的相关注解
        annotations.add("@Entity");

        final String tableName = introspectedTable.getFullyQualifiedTable().getIntrospectedTableName();

        annotations.add("@Table(name = \"" + tableName + "\")");

        return super.modelBaseRecordClassGenerated(topLevelClass,introspectedTable);
    }

    /**
     * 为JpaEntity的字段添加Jpa,注意,必须要有一个字段为id的自增主键
     */
    @Override
    public boolean modelFieldGenerated(Field field,TopLevelClass topLevelClass,IntrospectedColumn introspectedColumn,IntrospectedTable introspectedTable,ModelClassType modelClassType){

        final String columnName = introspectedColumn.getActualColumnName();

        final List<String> annotations = field.getAnnotations();

        //添加Jpa的相关注解

        if(field.getName().equalsIgnoreCase("id")){
            annotations.add("@Id");
            annotations.add("@GeneratedValue(strategy = GenerationType.AUTO)");
        }else {
            annotations.add("@Basic");
        }

        annotations.add("@Column(name = \"" + columnName + "\")");

        return super.modelFieldGenerated(field,topLevelClass,introspectedColumn,introspectedTable,modelClassType);
    }

    @Override
    public boolean validate(List<String> warnings){
        return true;
    }
}

将其配置进入配置文件后,运行:
在这里插入图片描述

五、总结

我演示了model和mapper的插件开发,但是为mapper增加方法的话需要修改xml配置文件,所以我分享一下我自己的工程:
https://gitee.com/chuyunfei/mybatis-generator-plugin.git
上面有这三个插件的源码,还有其他三个自定义的mapper方法,上面演示的工程是我在写博客的过程中一步步建立起来的,当然你可以直接运行我的git项目,不过记得改maven命令和那个jdbc驱动的位置。
如果有问题的话可以留言讨论哦,万一我不小心那个配置没有正确而对你造成困扰就很不好了,欢迎留言,么么哒。

——伤心流泪的不一定就是受害者,也许是加害者。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章