5.1 MyBatis代码生成器简介
在前两章可以看到,当使用MyBatis编写访问数据库的代码时,有很多与数据库表结构相关的固定规律的代码编写工作,这些枯燥的工作没有特别的技术含量,但工作量很大并且容易发生错误。为提高效率、避免人为错误, Mybatis 官方设计提供了代码生成器 Mybatis Generator (MBG) ,它可以在项目中自动生成简单的数据 CRUD 方法,甚至“无所不能”的高级条件查询(MyBatis3DynamicSql,有了它根本不需要 Mybatis Plus),让我们避免了进行数据库交互时需要手动创建对象和配置 Mybatis 映射等基础工作。
另外,MBG 有很好地扩展性,它提供了大量的接口和插件用来给我们自定义生成的代码应该是什么样子,例如我们可以自定义注释、代码格式化、添加 toString 方法等。
本章讨论如何应用MBG 来生成自己想要的代码,另外,我们也将认识强大的 MyBatis3DynamicSql 风格(它提供的条件类使用 Lambda 解耦,全注解,支持单表查询、多表查询、分页、排序、分组等等)。
5.2 配置与运行
5.2.1 依赖配置
MBG 支持使用Java 代码、maven 插件、Eclipse 插件等方式运行,本教程使用 maven 插件方式,所以需要在 build/plugins 节点引入 MBG 插件。另外,因为本文也需要用到 Java 程序来运行 MBG(当使用自定义类时),所以需要把插件的依赖项从 build/plugins/plugin 节点中单独取出来。
下面是前文生成的pom.xml文件的<build>部分的内容:
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build>
现在我们把已有的<plugins>包裹在<pluginManagement>内部,然后创建一个Mybatis Generator包含在新的<plugins>中:
<build><pluginManagement><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></pluginManagement><!-- mybatis-generator-maven-plugin插件需要放在和pluginManagement同级别 --><plugins><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.4.0</version><configuration><overwrite>true</overwrite></configuration></plugin></plugins></build>
5.2.2 插件参数详解
Mybatis Generator配置中的plugin/configuration 节点可以配置影响 MBG 行为的参数,如下:
| 参数 | 类型 | 描述 | 
|---|---|---|
| configurationFile | java.io.File | 配置文件的路径,默认为${basedir}/src/main/resources/generatorConfig.xml | 
| contexts | java.lang.String | 指定使用配置文件中的哪个context,用多个用逗号隔开 | 
| jdbcDriver | java.lang.String | JDBC 驱动 | 
| jdbcPassword | java.lang.String | JDBC 密码 | 
| jdbcURL | java.lang.String | JDBC URL | 
| jdbcUserId | java.lang.String | JDBC 用户名 | 
| outputDirectory | java.io.File | MBG 文件输出路径。只有在配置文件中配置targetProject=”MAVEN”(区分大小写),才会使用这个路径。 默认为${project.build.directory}/generated-sources/mybatis-generator  | 
| overwrite | boolean | 是否覆盖已经存在的同名Java文件。 如果为true,Java文件将被覆盖。 如果为false,MBG会将新生成唯一名称的Java文件(例如MyClass.java.1,MyClass.java.2) 默认为false  | 
| sqlScript | java.lang.String | 生成代码之前需要运行的SQL脚本路径。如果指定,则必须提供jdbcDriver,jdbcURL、jdbcUserId、jdbcPassword。 | 
| tableNames | java.lang.String | 需要生成代码的表,用多个用逗号隔开 | 
| verbose | boolean | 是否打印日志。默认为false | 
| includeCompileDependencies | boolean | 如果为true,则作用域为“ compile”,“ provided”和“ system”的依赖将添加到生成器的类路径中。默认为false | 
| includeAllDependencies | boolean | 如果为true,则所有作用域的依赖将添加到生成器的类路径中。默认为false | 
| skip | boolean | 
5.2.3 代码生成规则配置
使用 maven 插件的方式不需要编写代码,只要将规则配置到 generatorConfig.xml 文件就行,配置内容主要为:
- 如何连接到数据库
 - 生成什么对象,以及如何生成它们
 - 哪些表将用于对象生成
 
接下来在src/main/resources目录下创建新文件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>
    <!-- classPathEntry location="jdbc的jar包地址"/ -->
    <!-- !!!! Driver Class Path !!!! -->
    <classPathEntry location="/Users/david/Library/Application Support/JetBrains/IntelliJIdea2020.1/jdbc-drivers/MariaDB Connector J/2.6.0/mariadb-java-client-2.6.0.jar" />
    <!--
    targetRuntime
    MyBatis3DynamicSql:要求MyBatis3.4.2+,Java8+还需要依赖MyBatis Dynamic SQL选择该目标model为flat类型忽略defaultModelType配置。使用注解动态sql方式,不会生成XML文件。
    MyBatis3:要求MyBatis3.0+ Java5+生成XML配置文件和额外的Example类,Example不用写Sql就可以实现对单表的复杂查询操作。
    MyBatis3Simple:和MyBatis3类似,但是不会生成Example类。
    -->
    <!--  defaultModelType 这个是生成model的类型(实体类)
    flat 一个table对应一个model
    hierarchical 生成只含有主键的类,然后生成含有基本字段的类继承主键类, 生成只含有BLOB字段的类继承基础类,
    conditional 和 hierarchical类似,只不过如果主键只有一个字段,则会合并到基础类中 -->
    <context id="context" targetRuntime="MyBatis3" defaultModelType="flat">
        <!-- 生成Java文件的编码格式 -->
        <property name="javaFileEncoding" value="UTF-8"/>
        <!--   可以自己写插件,修改生成的代码 需要实现org.mybatis.generator.api.PluginAdapter    -->
        <!-- plugin type=""/ -->
        <commentGenerator>
            <property name="suppressAllComments" value="false" />
            <property name="addRemarkComments" value="true" />
            <property name="suppressDate" value="true" />
        </commentGenerator>
        <!-- 数据库驱动,不同的数据库驱动不一样,数据库链接URL、用户名、密码 -->
        <jdbcConnection driverClass="org.mariadb.jdbc.Driver"
                        connectionURL="jdbc:mysql://。。。。。"
                        userId="...."
                        password="....." />
        <!--   数据库类型和Java类型处理   -->
        <javaTypeResolver>
            <!-- 为true时数据库类型为DECIMAL/NUMERIC都会被映射为java.math.BigDecimal
            false(默认值) 自动根据精度映射 -->
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        <!-- 生成的实体类配置 -->
        <javaModelGenerator targetPackage="com.longser.union.cloud.data.model" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <sqlMapGenerator targetPackage="mybatis/mapper" targetProject="src/main/resources">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!-- 生成DAO的包名和位置 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.longser.union.cloud.data.mapper" targetProject="src/main/java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
        <table schema="cloud" tableName="member" domainObjectName="Member" />
        <!-- 一般我们可以简化为这样写,生成实体类会自动使用驼峰命名法.添加别名也是很有用的.
        这样生成的sql 为 col AS t_col,col2 AS t_col2 ... 我们在其他Mapper.xml文件则可以直接include引用,如果这个表字段更改了则不用修改其他Mapper.xml文件
        生成的BaseResultMap我们不要去修改,如果想要添加依赖关系,可以新定义个resultMap 使用extends去继承这个BaseResultMap
         -->
    </context>
</generatorConfiguration>
如果在 connectionURL 中存在类似 & 这样的字符,需要进行 URL 转义处理(& 转义结果为 &)
5.2.4 创建新的数据表
在数据库上用如下命令创建一个新的数据表
create table member
(
    id                int unsigned auto_increment
        primary key,
    union_id          int unsigned     default 1         not null comment '所在基层工会(201)编号',
    group_id          int unsigned     default 1         not null comment '分工会(300)或工会小组(400)编号',
    real_name         varchar(16)                        not null comment '会员真实姓名',
    identity_number   char(18) charset latin1            not null comment '身份证号',
    gender            tinyint unsigned default 1         not null comment '性别1男2女(与微信一致)',
    birthday          date                               not null comment '出生日期',
    month_of_birthday tinyint unsigned default 0         not null comment '生日的月份',
    zodiac            tinyint unsigned default 0         not null comment '生肖',
    nation            tinyint unsigned default 1         not null comment '民族',
    party             tinyint unsigned default 0         not null comment '政治面貌',
    mobile            char(11) charset latin1            not null comment '手机号码',
    email             varchar(128) charset latin1        null comment '电子邮件地址',
    degree            tinyint unsigned default 0         not null comment '最高学位',
    education         tinyint unsigned default 0         not null comment '最高学历',
    employment_state  tinyint unsigned default 1         not null comment '就业状态',
    health            tinyint unsigned default 1         not null comment '健康状况',
    member_state      tinyint unsigned default 1         not null comment '工会会籍状态',
    approval_status   tinyint unsigned default 0         not null comment '审核的状态',
    wechat_user_id    int unsigned     default 0         not null comment '微信用户表中的id',
    spouse_id         int unsigned     default 0         not null comment '配偶的会员编号',
    user_id           int unsigned     default 1         not null comment '数据录入用户的id',
    enabled           tinyint unsigned default 1         not null,
    deleted           tinyint unsigned default 0         not null,
    create_time       datetime         default curtime() not null,
    update_time       datetime         default curtime() not null
)
    comment '工会会员信息表' charset = utf8;
5.2.4 执行MBG生成代码
执行MBG的最简单的办法是在IDEA的最右边,打开Maven窗口,在mybatis-generator:generate上双击鼠标左键或在鼠标右键的上下文相关菜单中执行Run Maven Build
根据运行结果信息可见运行成功
浏览项目包内部的资源,可看到自动生成了四个文件
5.3 MBG 生成不同风格的代码
5.3.1 MBG代码风格定义
MBG 支持生成不同风格、不同语言的代码,例如,MBG 能够生成 Java 或 Kotlin 代码。MBG 支持生成旧版的 MyBatis3 风格(我们常用的 xml 配置属于 MyBatis3 风格,官方认为这种风格已经过时)上节我们设置的就是生成这种风格的代码。MBG也支持新版的 MyBatis3DynamicSql 的风格(这是官方推荐的风格)。
| 代码风格 | 描述 | 
|---|---|
| MyBatis3DynamicSql | 默认风格(官方推荐)。Java代码为全注解,不生成 XML 文件。 生成的高级条件查询灵活性较大,使用 lambda 表达式避免条件对象渗透到上一层。一个表生成一个实体类。  | 
| MyBatis3 | 这是官方已经不再推荐使用的早期风格。能够生成 MyBatis3 兼容的XML映射文件和实体类,也可以生成全注解方法的Java类。 生成的高级条件查询灵活性较小,条件类渗透到上一层,而且SQL和代码耦合度较高 一个表除了生成基本类,可能还会生成主键类和BLOB类(如果指定的话)。  | 
| MyBatis3Simple | 这是MyBatis3 的简易版。能够生成 MyBatis3 兼容的XML映射文件和实体类,也可以生成全注解方法的Java类。不生成 “by example” 或 “selective” 的方法。一个表生成一个实体类。 | 
由于 MyBatis3 风格生成的 Example 类存在的问题,实际项目中建议使用 MyBatis3Simple 风格或官方推荐的 MyBatis3DynamicSql 风格。
5.3.2 普通全注解风格代码
现在把上一节中的 做如下修改
context的targetRuntime属性值改为”MyBatis3Simple“javaClientGenerator的type属性值改为”ANNOTATEDMAPPER“
手工删除6.2中生成的文件然后再次执行MBG,可以看到这次只生成了两个文件
打开MemberMapper.java文件,可以看到它是一个经典的全注解映射文件,下面是一个代码片段
    @Delete({
        "delete from member",
        "where id = #{id,jdbcType=INTEGER}"
    })
    int deleteByPrimaryKey(Integer id);
5.3.3 我们的选择
根据当前各项目的实际情况,我们选择生成普通全注解风格代码(即6.3.2中的设置)。
5.4 自定义注释生成器
默认的注释生成器比较鸡肋。下面简单举个例子,MBG 生成的实体类属性的注解是这样的:
    /**
     * Database Column Remarks:
     *   会员真实姓名
     *
     * This field was generated by MyBatis Generator.
     * This field corresponds to the database column member.real_name
     *
     * @mbg.generated
     */
    private String realName;
这种注释看起来并不好,很多人都会考虑自己实现。
想要自定义注释生成器需要实现org.mybatis.generator.api.CommentGenerator接口并提供无参构造,在本教程中,我们在DefaultCommentGenerator的基础上改造了一个注释生成器。
注意,编写自定义注解生成器时应该考虑在xml节点的注释中加入@mbg.generated来维持 MBG 文件合并的功能。本节主要讨论如何使在MBG中使用自定义注释生成器。注释生成器的具体代码详见教程附件。
当包含自定义注释生成器的Jar文件包已经部署到本地的Repository以后,给mybatis-generator-maven-plugin增加依赖(注意不是项目的依赖部分):
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.4.0</version>
                <configuration>
                    <overwrite>true</overwrite>
                </configuration>
+               <dependencies>
+                   <dependency>
+                       <groupId>com.longser.mybatis</groupId>
+                       <artifactId>generatorr</artifactId>
+                       <version>1.0</version>
+                   </dependency>
+               </dependencies>
            </plugin>
然后在 generatorConfig.xml 中 把 commentGenerator 改成如下内容
        <commentGenerator type="com.longser.mybatis.generator.MyCommentGenerator">
            <property name="suppressAllComments" value="false" />
            <property name="addRemarkComments" value="true" />
            <property name="suppressDate" value="false" />
        </commentGenerator>
按照4.2.4中的方法运行MBG完成后可以看到实体类属性的注释变成我们想要的样子。
/**
 * 
 * 工会会员信息表
 * 
 * @author david
 * @version 1.0
 * @since Tue Nov 02 12:49:12 CST 2021
 * 
 * Copyright by Longser Technologies Ltd. 
 */
public class Member {
    /**
     */
    private Integer id;
    /**
     * 所在基层工会(201)编号
     */
    private Integer unionId;
    /**
     * 分工会(300)或工会小组(400)编号
     */
    private Integer groupId;
    /**
     * 会员真实姓名
     */
    private String realName;
5.5 关于 MBG 文件覆盖的问题
当我们在迭代开发环境中使用 MBG,需要注意文件覆盖的问题,默认情况下,文件覆盖规则如下:
如果 Java 或 Kotlin 文件已经存在,MBG 可以覆盖现有文件或使用其他唯一名称保存新生成的文件,这取决于你在插件配置中如何定义
 。如果 XML 已经存在,MBG 会采用文件合并的方式。它不会修改你自定义的节点,但是会更新原来生成的 CRUD 节点(如果表发生变化)。文件合并有个前提,就是原来生成的 CRUD 节点必须包含 @mbg.generated 的默认注释。否则,当再次运行 MBG 时,它将无法识别哪些是它生成过的节点,于是会出现下图的情况,即 CRUD 节点被重复插入。
5.6 MyBatis3DynamicSql 风格 API 的使用
MyBatis官方推荐动态SQL风格。尽管我们不打算选择这种模式,还是还是讨论一下。
5.6.1 生成SQL风格代码
把context的targetRuntime属性值设置为MyBatis3DynamicSql,手工删除已经生成的文件然后再次执行MBG,此时会多生成一个文件MemberDynamicSqlSupport.java,这个类有下面两个引用
import org.mybatis.dynamic.sql.SqlColumn;
import org.mybatis.dynamic.sql.SqlTable;
为了完成这两个引用,需要在pom.xml中增加如下的依赖
        <dependency>
            <groupId>org.mybatis.dynamic-sql</groupId>
            <artifactId>mybatis-dynamic-sql</artifactId>
            <version>1.2.1</version>
        </dependency>
5.6.2 高级查询的处理
Mybatis3Simple 可以生成简单的 CRUD,但是针对高级条件查询就无能为力了,实际项目中,我们有时必须手动地去编写高级查询的代码,通常情况下,我们用不同的方案来处理:
- 自定义代码生成器来生成高级条件查询的代码。针对复杂场景时,我们还是得手动改造。
 - 使用 Mybatis Plus 的条件构造器。在 MP 3.0 之前,条件构造器会造成 sql 和代码的严重耦合。
 
现在,Mybatis 官方为我们提供了新的方案,那就是 MyBatis3DynamicSql。MyBatis3DynamicSql 风格丢弃了 XML 文件,使用全注解形式并搭配几个条件辅助类,刚接触人往往会比较抗拒,因为前面MyBatis3 中也尝试过全注解和条件类,当遇到某些复杂场景时,还是需要 XML,而且生成器提供的条件类会渗透到服务层。
如果真的全面使用MyBatis3DynamicSql,你会发现它的强大。它可以做到:
- 单表高级查询。包括 Equal/NotEqual、Like/NotLike、In/NotIn、Between/NotBetween、IsNull/IsNotNull 等等,而且还可以判空设置条件。
 - 联表查询。你可以像给单表一样给关联表设置条件。
 - 分组、排序、分页。
 - 只返回你要的字段。
 
下面这个例子,涉及到了关联查询、排序、分页,而 MyBatis3DynamicSql 都能帮我们处理,并且它利用 Lambda表达式来解耦条件类。
  @Test
  public void testSelect() {
    // 注意,当查询结果多于1时会报错
    List<Employee> lsit = baseMapper.select(c ->
      c.leftJoin(DepartmentDynamicSqlSupport.department)
      .on(departmentId, new EqualTo(DepartmentDynamicSqlSupport.id))
      .where(name, isLikeWhenPresent("zzs%"), or(name, isLikeWhenPresent("zzf%")))
      .and(status, isEqualTo((byte)1))
      .and(address, isIn("北京", "广东"))
      .and(DepartmentDynamicSqlSupport.name, isEqualToWhenPresent("质控部"))
      .orderBy(gmtCreate.descending())
      .limit(3)
      .offset(1)
    );
    lsit.forEach(System.err::println);
  }
MyBatis3DynamicSql 风格未来可能会被更多开发者使用,这里就不长篇大论的讲解如何使用它,它的 API 并不难操作。
5.7 参考资料
MyBatis Generator的官方链接 http://mybatis.org/generator
版权说明:本文由北京朗思云网科技股份有限公司原创,向互联网开放全部内容但保留所有权力。

