☰
MyBatis Generator 2021最新版配置详解与实战避坑指南
2026/9/30 1:01:38 网站建设 项目流程

今年把项目里用了几年的 MyBatis Generator 重新梳理了一遍,发现网上很多教程还停留在 Eclipse 插件时代,或者直接照搬老版本配置。实际上 3.5.x 之后,这个工具在 Maven 插件、类型转换、自定义注释上的玩法已经变了不少。这篇不是那种从零开始一行行念文档的入门贴,我尽量把“文档里不会写但实操一定会遇到”的东西讲清楚,比如怎么避免覆盖手写代码、时间类型映射怎么调、多个数据源怎么配、生成完怎么验证。适合刚接触 MyBatis Generator 的新人,也适合已经用了很久但一直踩着默认配置的老手。

1. 为什么还要用 mybatis-generator,以及它能干掉哪些重复劳动

1.1 核心需求解析:实体、Mapper、Example 文件到底生成了些什么

先说结论:MyBatis Generator 的职责非常单一——读取数据库表结构,生成对应的 Java Bean、Mapper 接口、Mapper XML,以及对单表做 CRUD 的通用方法。它不负责业务逻辑,更不是 ORM 框架,只是一个把“建表之后写基础代码”这个重复动作自动化的生成器。

以一张常见的 user 表为例,包含 id、username、password、email、create_time、update_time 这 6 个字段,运行生成器后会自动产生 5 个文件:

生成文件作用说明
User.java对应表的实体类,每个字段一个属性,包含 getter/setter
UserExample.java查询条件构造器,用来拼 where 条件
UserMapper.javaMapper 接口,定义了单表操作的抽象方法
UserMapper.xmlSQL 映射文件,实现接口方法对应的具体 SQL
UserDao.java(可选)旧版本风格的 Dao 接口,新版基本不生成,可忽略

很多第一次用的人会被 UserExample 这个类吓到,觉得它特别重。但它其实是 MyBatis Generator 最实用的设计,举例来说,如果要查“用户名是 admin 并且创建时间在今天之后”的记录,传统做法是手写一个<select>加上动态 SQL 判断,而 Example 类允许这样写:

UserExample example = new UserExample(); UserExample.Criteria criteria = example.createCriteria(); criteria.andUsernameEqualTo("admin"); criteria.andCreateTimeGreaterThan(new Date()); List<User> users = userMapper.selectByExample(example);

这样的代码可读性很高,而且条件拼接完全基于 Java 对象,不依赖 XML 里的<if>判断,对于单表复杂查询特别友好。所以熟悉生成器产物的第一件事,不是盯着 Mapper.xml 看,而是先搞懂 Example 类里那些andXxxEqual、andXxxLike方法是怎么来的——本质上就是表字段名转成驼峰后,加上各种查询条件后缀自动生成的。

1.2 版本演进与选型思路:2021 年到底该用哪个版本

标题里写了“2021 最新版”,这里我展开解释一下。MyBatis Generator 是 MyBatis 官方提供的子项目,当前的主线版本是 3.5.x 系列。3.5.0 版本在 2019 年发布,最大变化是支持了基于 Java 8 的 JSR 310 时间类型(LocalDate、LocalDateTime),解决了一直以来日期类型映射成Date的痛点。后续 3.5.1、3.5.2、3.5.3 陆续修复了一些插件机制和 XML 生成格式的 bug。到了 3.5.6、3.5.7 之后,整个工具已经比较稳定,配置方式也没再发生过破坏性变更。

如果你在 2021 年之后看了这篇笔记,我建议直接用当时的最新稳定版,比如 3.5.9 或更新的 3.5.x。这类工具没有追求最新版的必要,但也不能用太久远的版本,否则会遇到两个非常实际的问题:

  • 高版本 MySQL 驱动(Connector/J 8.x)和老版本生成器之间,偶发时区解析异常。
  • 数据库字段类型如果使用了 JSON、ENUM、MEDIUMINT 这类较新的类型,老版本可能不认识,直接降级成Object。

所以我的选择建议是:生成器用 3.5.9 左右,数据库驱动用 mysql-connector-java 8.0.x 或 8.4.0,JDK 用 8 以上即可,这套组合在大部分项目里实测下来很稳。

2. 使用前的环境准备与核心配置拆解

2.1 环境准备清单:JDK、数据库驱动、Maven 插件

实际操作前,需要确认本机环境满足以下条件。这里我用的是 Maven 工程,因为这是目前最主流也最方便的项目管理方式,强烈建议不要再用 Eclipse 的第三方插件,一方面它停止维护很久了,另一方面 Maven 插件在 CI/CD 环境里也能直接执行。

环境清单如下:

环境项版本/说明
JDK1.8 或以上,推荐 8/11/17
Maven3.5 或以上
数据库MySQL 5.7 或 8.x,其他数据库也支持,但配置略有差异
数据库驱动mysql-connector-java 8.0.x
生成器插件mybatis-generator-maven-plugin(版本和 core 保持一致)

接入 Maven 的时候,需要在 pom.xml 的 build/plugins 中加入插件。这里有一个容易犯的错误:很多人只加了插件,却没有在插件的 dependencies 里显式指定数据库驱动。如果你本地的 Maven 仓库没有默认加载 mysql 驱动,运行时会直接报 ClassNotFoundException。正确的配置长这样:

<plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.4.2</version> <configuration> <configurationFile>src/main/resources/generatorConfig.xml</configurationFile> <overwrite>true</overwrite> <verbose>true</verbose> </configuration> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> </dependencies> </plugin>

注意<overwrite>这个参数。设置为 true 时,每次运行都会覆盖同名的实体类、Mapper 接口、XML 文件。听起来没什么问题,但如果你在生成之后手写过这些文件里的方法,再运行一次就会全部丢失,连提醒都没有。所以项目规范里一定要规定:生成器只负责生成基础 CRUD,手写扩展方法必须放到别处,或者在生成后整理好再覆盖。

2.2 generatorConfig.xml 核心配置逐项说明

generatorConfig.xml 是整个工具的核心,它决定了生成哪些表、生成到哪个包、是否生成注释、如何映射类型。下面是一份对 MySQL 比较完整的配置,我按模块拆开说明。

首先是最外层结构:

<?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,用于指定驱动 jar 的绝对路径,如果 Maven 中已声明则可以不写 --> <context id="mysqlContext" targetRuntime="MyBatis3Simple" defaultModelType="flat"> ... </context> </generatorConfiguration>

defaultModelType有几个值,我逐一说明。老的文档通常推荐conditional,意思是一张表只会生成一个实体类,如果表只有一个主键且没有其他字段,不会生成单独的 Example 类(这个主键查询会合并到实体里面)。但实际项目中我们经常要按非主键字段做条件查询,所以我更推荐用flat,它让每张表固定生成 实体 + Example + Mapper 三件套,逻辑一致,不会出现某些表有 Example、某些表没有的混乱局面。

在<context>内部,还需要配置注释生成器、JDBC 连接、类型处理器、模型生成器、SQL 映射生成器、客户端生成器、表配置。我挑几个容易踩坑的细说。

注释生成器这块,默认的注释是@author加当前用户名,还会带上生成时间,导致代码每次生成都不一样,对版本控制很不友好。实际情况中,团队项目一般会自定义注释,把作者写死或者干脆不要注释。如果你不想写 Java 类去自定义,可以用一个轻量方案,直接注释掉 commentGenerator 配置,或者设置:

<commentGenerator> <property name="suppressAllComments" value="true"/> </commentGenerator>

但完全关闭注释有个副作用:XML 里没有任何说明,后续维护时别人看不出这个文件和生成器的关联。我更推荐保留注释,但抑制自动生成的作者和时间戳,这个需要自定义一个类,后面在《进阶手段》章节里我会给完整代码。

JDBC 连接配置没什么悬念,但有两个隐患值得注意。第一个是useInformationSchema,MySQL 8.x 下建议设置为 true,否则生成器读取表注释、字段注释时可能抓到空值。第二个是连接串参数,最好显式加上nullCatalogMeansCurrent=true&useSSL=false&serverTimezone=Asia/Shanghai。前者是为了防止 MySQL 驱动默认返回的 catalog 包含全部库而导致生成器找不到表,后者是为了避免时区报错。

<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/your_db?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;nullCatalogMeansCurrent=true" userId="root" password="your_password"> <property name="nullCatalogMeansCurrent" value="true"/> </jdbcConnection>

类型解析器可以配置强制 Java 类型映射,比如把数据库的TINYINT(1)映射为Boolean,把DECIMAL(10,2)映射为BigDecimal。直接给一份常用配置:

<javaTypeResolver> <property name="forceBigDecimals" value="true"/> <property name="useJSR310Types" value="true"/> </javaTypeResolver>

forceBigDecimals设置为 true 后,所有小数类型都会映射成 BigDecimal,避免精度丢失。useJSR310Types对应 Java 8 时间类型,开启后datetime映射为LocalDateTime,date映射为LocalDate,time映射为LocalTime。这里提醒一下,如果你的项目还在用 Date 类型,或者还在用 MyBatis 3.4.x 而没升级到 3.5.x,就必须把useJSR310Types设置为 false,否则 MyBatis 对 JSR310 类型的支持不完整,会引出类型处理器找不到的报错。

接下来是三个生成器的核心配置,直接决定代码输出位置:

<javaModelGenerator targetPackage="com.demo.model" targetProject="src/main/java"> <property name="trimStrings" value="true"/> </javaModelGenerator> <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"/> <javaClientGenerator type="XMLMAPPER" targetPackage="com.demo.mapper" targetProject="src/main/java"/>

targetProject相对路径的坑非常大。Maven 环境下你可能有src/main/java和src/main/resources两个目录,如果不小心把 SQL 映射文件生成到src/main/java下,Maven 默认不会把它打包进最终产物,运行时会提示 “Invalid bound statement (not found)”。这个坑我至少踩过两次。另外,如果项目结构是多 Module 的,targetProject 需要写模块的相对路径,例如module-common/src/main/java,不能直接写src/main/java。

table配置是最后一步。默认情况下,生成器会扫描连接库中的所有表,如果你想精确到某几张表,可以用tableName指定:

<table tableName="user" domainObjectName="User" enableInsert="true" enableUpdateByPrimaryKey="true" enableDeleteByPrimaryKey="true" enableSelectByPrimaryKey="true" enableCountByExample="true" enableUpdateByExample="true" enableDeleteByExample="true" enableSelectByExample="true" selectByExampleQueryId="true"/>

enableXxx系列开关因人而异。如果项目里单表操作非常简单,不需要 Example 类的各种 byExample 方法,可以全部关闭,减少生成代码量。但我个人建议保留selectByExample和countByExample,因为分页查询、条件统计这两类需求在业务开发里几乎一定会出现,等用到的时候再手写反而费事。

3. 三种运行方式与完整实操过程

3.1 方式一:Maven 插件运行

把配置文件写好之后,运行命令:

mvn mybatis-generator:generate

这是最常规的方式。如果你在 IDEA 右侧的 Maven 面板里找不到这个插件对应的节点,可以先mvn clean一下刷新项目,或者在终端里直接执行。运行成功后,控制台一般会输出 “BUILD SUCCESS” 并列出生成了哪些文件。

我的建议是,为了不污染主代码,第一次使用可以先把targetProject指向一个临时目录,比如src/main/java-tmp,生成完之后人工 review 一下实体的字段映射、Mapper 接口的方法命名是否符合项目规范,确认没问题再统一移动到正式目录。这个过程虽然多了一步,但能发现很多隐藏问题,例如某些表没有主键导致生成失败、字段前缀引发的命名不一致。

3.2 方式二:Java 代码直接调用

如果你不想依赖 Maven 插件,或者需要把生成动作集成到自己的管理平台里,可以直接写 Java 代码调用。适合那种“数据库表结构一变动,系统自动重新生成基础代码”的场景。我一般这样写:

public class GeneratorLauncher { public static void main(String[] args) throws Exception { List<String> warnings = new ArrayList<>(); boolean overwrite = true; File configFile = new File("src/main/resources/generatorConfig.xml"); ConfigurationParser cp = new ConfigurationParser(warnings); Configuration config = cp.parseConfiguration(configFile); DefaultShellCallback callback = new DefaultShellCallback(overwrite); MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings); myBatisGenerator.generate(null); for (String warning : warnings) { System.out.println("WARNING: " + warning); } } }

这种方式的优越性在于控制力更强。你可以传入自定义的ProgressCallback,实时看到每条 SQL 的生成进度;还可以在generate(null)阶段做额外的日志记录。Maven 插件本质上也是封装了这段逻辑,只是把配置路径和 overwrite 参数暴露成了插件配置项。

需要注意DefaultShellCallback的行为:当 overwrite 为 true 时,它会直接覆盖文件,不会做任何备份。所以如果你用 Java 方式生成,建议在配置里把输出目录和正式源码目录分离,先生成到临时目录,人工 review 一遍再同步。

3.3 方式三:命令行方式

命令行方式适合在没有 IDE 的服务器或 CI 流程里自动执行。Maven 插件本身就是命令行友好型,但如果你希望独立于项目单独运行,也可以使用官方发布的 jar 包:

java -jar mybatis-generator-core-1.4.2.jar -configfile generatorConfig.xml -overwrite

使用前需要确保 classpath 下包含数据库驱动。通常在服务器上执行时,我会把驱动 jar 和生成器 jar 放在同一个目录,然后运行:

java -cp mybatis-generator-core-1.4.2.jar:mysql-connector-java-8.0.33.jar \ org.mybatis.generator.api.ShellRunner \ -configfile generatorConfig.xml -overwrite

命令行方式的优点不止是脱离 IDE,更在于它很适合接入到 git hooks 或者 Jenkins 流水线里。比如项目里约定“所有表结构变更必须执行一次生成”,就可以在 CI 脚本里加一个步骤,自动生成新表的基础代码,然后提交到仓库。这样多人协作时大家拿到的都是同一份生成产物,不会出现有人用旧版本、有人用新版本导致的字段缺失问题。

3.4 生成结果的检查与验证

不论用哪种方式,生成完成之后都建议做三件事。第一,检查实体类的字段注释是否完整、类型是否符合预期,重点看时间字段是不是LocalDateTime、金额字段是不是BigDecimal;第二,检查 Mapper 接口的方法命名是否可读,比如selectByPrimaryKey而不是selectByPrimary,不对的就要回头检查表配置;第三,把工程跑起来的核心验证方式是写一个单测,或者直接在 Service 层调用一个简单查询,避免启动时才发现 Mapper 扫描包路径没有覆盖到新生成的位置。

我之前带过一个同事,生成后直接把文件放进项目里,结果运行时报 SQL 映射找不到。查了半天才发现他把sqlMapGenerator的 targetProject 写成了项目根目录,XML 文件被生成到了workspace/resources下面,压根不在 classpath 中。所以验证环节不能偷懒,尤其是第一次配置的时候,时间再紧也要走一遍单测。

4. 常见问题与排查技巧实录

4.1 报错速查表

我把这几年用 MyBatis Generator 遇到的报错整理成一张表,基本覆盖了大部分常规场景:

报错信息原因分析解决方案
Table 'xxx' doesn't exist连接串里未指定库名,或表名大小写问题检查 JDBC 地址,MySQL 下确认 useInformationSchema 配置
No suitable driver found插件未引入数据库驱动在插件 dependencies 中显式添加 mysql-connector-java
The specified target project directory X does not existtargetProject 路径不存在或模块路径写错确认目录存在,多模块下写清楚模块前缀
Cannot connect to database server网络不通、账号密码错、防火墙拦截先用数据库客户端连接验证
Unsupported charset连接串缺少 characterEncoding 或数据库字符集特殊连接串加 characterEncoding=utf8,或调整库字符集
Duplicate class name两张表映射了同一个 domainObjectName检查 table 配置,避免命名冲突
The JDBC driver has been forcibly unregistered高版本驱动未处理驱动反注册,一般不影响生成忽略或升级到官方 8.0.33 以上

4.2 覆盖与备份机制的正确玩法

覆盖问题是所有代码生成工具都绕不开的话题。MyBatis Generator 默认没有备份机制,overwrite=true的时候,同名文件直接替换。项目中如果已经手写了 Mapper 扩展方法,一种常见的做法是把扩展方法写到另一个接口里,然后让 Mapper 接口继承它:

public interface ExtUserMapper { List<User> selectUserWithOrders(@Param("userId") Long userId); } public interface UserMapper extends ExtUserMapper { // 生成的 CRUD 方法 }

这样即使生成器把 UserMapper 覆盖了,扩展方法也不会丢。另一种做法是约定好“生成后不要改动生成的文件”,所有的自定义 SQL 都写到独立的 XML 文件里,比如user-ext.xml。缺点是手写 SQL 需要自己保证 namespace 不冲突,但好处是生成器怎么重新执行都不会影响已有逻辑。

从工程管理角度,我建议团队内部统一一种方式,并在 README 里写明“执行生成器后必须检查 git diff”,防止有人误覆盖重要手写代码。生成前顺手git add -A和git commit -m 'backup before regenerating'也是一个我自己的小习惯,成本极低,收益极高。

4.3 大小写敏感、关键字冲突与特殊字段处理

MySQL 在 Linux 下表名是区分大小写的,Windows/macOS 默认不区分。如果你的开发机是 Windows,数据库在 Linux 上,很容易出现本地生成正常、线上生成找不到表的问题。此时在 table 配置里显式指定 schema(或 tableCatalog)能规避大部分问题:

<table schema="your_db_name" tableName="user" domainObjectName="User"> </table>

关键字冲突也是老生常谈。比如表字段叫order、group、desc,直接生成 SQL 的时候可能出现语法错误。MyBatis Generator 默认会对部分保留字加反引号,但为了保险,最好在字段映射上手动指定列名别名:

<columnOverride column="order" property="orderNo" />

这个配置会改变生成的 Java 属性名和 XML 里的列引用,但又不会要求你改数据库字段名。对于order这种保留字,生成后的 XML 中 select、insert 语句都会正确添加反引号,避免 SQL 执行时直接报错。

4.4 多数据源场景下的配置组织

很多后台系统会同时连接多个数据库,比如主库和报表库。MyBatis Generator 的context节点天然支持多套配置,你可以在同一个 generatorConfig.xml 里配置两个context,指定不通的 connectionURL:

<context id="mainDb" targetRuntime="MyBatis3Simple" defaultModelType="flat"> <jdbcConnection ... connectionURL="jdbc:mysql://localhost:3306/main_db"/> <javaModelGenerator targetPackage="com.demo.model.main" .../> ... </context> <context id="reportDb" targetRuntime="MyBatis3Simple" defaultModelType="flat"> <jdbcConnection ... connectionURL="jdbc:mysql://localhost:3306/report_db"/> <javaModelGenerator targetPackage="com.demo.model.report" .../> ... </context>

运行一次mvn mybatis-generator:generate会按顺序执行两个 context。这里需要注意,有时两个库存在同表名、同字段名,生成的每个 context 都要单独确认 targetPackage 不重复,否则容易出现重复类编译失败。另外,不同数据库方言的表注释查询方式不一样,像 Oracle、PostgreSQL、SQL Server 的方言配置都和 MySQL 不同,项目里如果同时接多种数据库,要分别为每个 context 调整类型解析器。

4.5 代码生成之后运行时报错的快速定位法

生成只是第一步,生成后的代码是否真的能跑通是更重要的一关。运行时报错最常见的三类:

  • org.apache.ibatis.binding.BindingException: Invalid bound statement (not registered)。这代表 Mapper 接口和 XML 文件没有正确绑定。排查链路是先确认 XML 的 namespace 要与接口全限定名一致,再看 XML 文件是否在 classpath 下,最后看 MyBatis 配置里的 mapperLocations 是否包含对应路径。
  • java.lang.ClassCastException: java.sql.Timestamp cannot be cast to java.time.LocalDateTime。这个通常是类型处理器缺失。如果用 JSR310 类型又没引入对应的 mybatis-typehandlers-jsr310 依赖,或者 MyBatis 版本低于 3.4.5,任何 LocalDateTime 字段都可能出问题。
  • Unknown column 'create_time' in 'field list'。这种多半是配置文件里启用了列名和属性名的严格映射,但生成的 XML 中使用了默认命名策略,实际插入语句里出现了错误的列名。排查方向集中在 table 配置里是否启用了useActualColumnNames、enableInsert等。

排查工具层面,推荐开启 MyBatis 的 SQL 日志来观察生成的语句,比如:

logging: level: com.demo.mapper: debug

把 Mapper 包日志级别开到 debug,控制台会输出每条 SQL 的 PreparedStatement 参数,能清晰定位是列缺失还是参数没传进去。这个方法比反复阅读 XML 要快得多。

5. 让生成结果更贴合项目规范的进阶手段

5.1 自定义注释方案

默认注释生成出来的东西长这样:

/** * This class was generated by MyBatis Generator. * This class corresponds to the database table user * @generated Wed Jun 01 10:00:00 CST 2022 */

每次生成时间都不一样,代码评审时 diff 很大。自定义注释的方式是继承 DefaultCommentGenerator 并重写 addModelClassComment 和 addGeneralMethodComment 等几个方法。一个基本实现:

package com.demo.mybatis.generator; import org.mybatis.generator.api.CommentGenerator; import org.mybatis.generator.api.IntrospectedTable; import org.mybatis.generator.api.dom.java.*; import org.mybatis.generator.internal.DefaultCommentGenerator; import java.util.Properties; public class CustomCommentGenerator extends DefaultCommentGenerator { private final String author = "team-code"; @Override public void addModelClassComment(TopLevelClass tc, IntrospectedTable introspectedTable) { tc.addJavaDocLine("/**"); tc.addJavaDocLine(" * " + introspectedTable.getRemarks()); tc.addJavaDocLine(" *"); tc.addJavaDocLine(" * @author " + author); tc.addJavaDocLine(" * @date " + LocalDate.now()); tc.addJavaDocLine(" */"); } @Override public void addGeneralMethodComment(Method method, IntrospectedTable introspectedTable) { method.addJavaDocLine("/**"); method.addJavaDocLine(" * " + method.getName()); method.addJavaDocLine(" */"); } }

然后在 generatorConfig.xml 的<commentGenerator>节点指定全限定类名:

<commentGenerator type="com.demo.mybatis.generator.CustomCommentGenerator"/>

这种自定义的好处不只是注释好看,更关键的是可以按团队规范统一字段说明。上面代码里introspectedTable.getRemarks()会读取表注释,这个动作依赖 JDBC 连接里useInformationSchema=true。如果没有设置这一项,这里拿到的往往是一个空字符串,所以注释生成和 JDBC 配置是联动关系。

5.2 自动补充逻辑删除与乐观锁字段

实际业务表里基本都会有逻辑删除字段(is_deleted)和版本号(version)。MyBatis Generator 不会自动识别这些字段的业务含义,但可以通过在实体里额外生成方法来统一处理。比较轻量的做法是使用自定义插件,在生成的 Model 类里增加默认值设置:

package com.demo.mybatis.generator.plugin; 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.TopLevelClass; import java.util.List; public class DefaultValuePlugin extends PluginAdapter { @Override public boolean validate(List<String> warnings) { return true; } @Override public boolean modelFieldGenerated(Field field, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable) { if ("isDeleted".equals(introspectedColumn.getJavaProperty())) { field.setInitializationString("0"); } if ("version".equals(introspectedColumn.getJavaProperty())) { field.setInitializationString("1"); } return super.modelFieldGenerated(field, topLevelClass, introspectedColumn, introspectedTable); } }

配置到生成器后,新生成的实体里 isDeleted 字段默认值是 0,version 字段默认值是 1。这个插件不需要每个项目都写一遍,建议沉淀到团队的公共模块里,做成二方库引入,这样新项目接入时直接复用。类似的场景还有“创建时间自动填充当前时间”,可以在 insert 语句生成时通过 SQL 片段控制,不一定非要在生成器层面做,用数据库 DEFAULT CURRENT_TIMESTAMP 更省心。

5.3 多表关联查询的补充策略

MyBatis Generator 是单表代码生成器,它不会帮你生成 join 查询。但业务系统不可能都是单表操作,所以在团队中我形成了这样的分工:

  • 单表 CRUD,用生成器产物直接完成。
  • 两表以上 join 的只读查询,在独立的扩展 XML 里手写 SQL,复用生成的 ResultMap 做映射。
  • 复杂写操作(批量插入、批量更新、insert or update),在手写 SQL 的基础上配合自增主键回填。

复用 ResultMap 这一点很容易被忽略。如果你在user-ext.xml里又写了一个<resultMap id="UserWithRoleMap",然后发现和生成的 UserMapper.xml 里字段映射不一致,排查起来非常痛苦。正确做法是手写 SQL 的resultMap直接引用生成好的BaseResultMap,结构统一,字段不会丢。如果你用的是注解方式,也可以在 Mapper 接口里写@ResultMap("com.demo.mapper.UserMapper.BaseResultMap"),这样手写@Select查询也能复用生成的字段映射关系。

5.4 增量生成与表结构变更的协同流程

数据库表结构是不断变化的,今天加一列,明天改一个字段类型,生成器必须能应对这种“增量变更”。这里要明确一点:MyBatis Generator 是整体覆盖,不是增量合并。也就是说,它不会智能地保留你新增的手写方法,只会按照当前表结构重新生成一份文件。所以表结构变更时,标准的处理流程是:

  1. 先检查当前分支的 git 状态,确认没有未提交的手写改动落在将要被覆盖的文件里。
  2. 先执行一次 git diff 备份当前生成文件的内容,方便出问题时恢复。
  3. 运行生成器,然后立刻 review 所有 diff,重点看新增字段的映射和类型转换是否符合预期。
  4. 如果只是新增了一个字段,生成器会更新实体类和 XML 的 ResultMap、Base_Column_List 等位置,手写的扩展查询不需要改动,因为它引用的是 ResultMap 的 id,而 id 不会变。
  5. 如果删除了某个字段,一定要排查手写 SQL 中是否还用到该列,否则运行期会报 Unknown column。

增量生成的另一个细节是小版本升级问题。生成器的版本升级后,生成的格式可能会变化,例如注释风格、XML 缩进、方法顺序等。升级后第一次生成,diff 会非常大,但大多只是格式层面的差异。这种时候建议不要一次性把生成结果全部提交,而是先用临时目录生成,人工比对确认业务影响。

5.5 与 MyBatis-Plus 生成器互换的参考

很多团队现在用的是 MyBatis-Plus,它的代码生成器(MybatisPlusGenerator)也能生成实体、Mapper、XML,而且默认带有逻辑删除、乐观锁注解。这里补充一句:如果你的项目已经引入 MyBatis-Plus,直接用它自带的生成器会更省事;如果项目还在用原生 MyBatis 并且不想为此引入一套 ORM 增强框架,那么 MyBatis Generator 依然是轻量可靠的选择。

两者互换有一个常见问题:MyBatis-Plus 生成的实体默认标注@TableName、@TableId等注解,而原生 MyBatis Generator 生成的实体完全靠 XML 描述映射关系。如果团队中间切换过框架,数据库表名和 Java 字段映射就很容易出现二义性。所以我个人的建议是:团队框架选型确定后就不要再无谓地切换生成器,哪怕功能上 MyBatis-Plus 生成器更全面,频繁切换的迁移成本远大于工具本身带来的收益。如果你实在想用 MyBatis-Generator 生成模型,再交给 MyBatis-Plus 做操作,也不是不行,但要做好@TableName注解依赖和默认驼峰映射的匹配,这又是一个不小的工程。

6. 实操心得:从“能用”到“好用”的关键一跃

按照整篇笔记的顺序走下来,其实你会发现 MyBatis Generator 本身的学习曲线并不陡,关键在于能不能把“生成”这个动作融入整个项目的开发规范里。在我所在的团队,我们制定的规范大概是这样:

  • 所有基础单表 CRUD 一律使用生成器产物,禁止手写重复的insert、updateByPrimaryKey这类方法。
  • 手写的扩展 SQL 必须放到独立 XML 或使用注解方式实现,绝不直接修改生成文件。
  • 表结构变更后,必须由 DDL 提交人在 24 小时内执行一次生成器并提交 diff,避免其他人依赖旧结构写出错误代码。
  • 生成器配置文件和自定义插件统一提交到项目工程里,作为基础设施管理,不允许各人本地私藏一套配置。

这个规范执行了大概半年,最大的感受就是大家代码里“低级查询”的写法变得非常一致,评审代码时可以更集中地关注业务逻辑,而不是浪费在“这里为什么不用 selectByPrimaryKey”这种事情上。另外,因为生成产物是标准化的,团队在培训新人时的负担也小很多。

最后有机会的话,可以再跑一次mvn mybatis-generator:generate,然后用 git 看看 diff 里到底发生了什么变化,你能更直观地理解这个工具的每个配置项带来的真实影响。其他再深的东西,比如插件 API 文档、自定义类型解析器,用到的时候再按官方文档补就行,不需要一开始把每个扩展点都啃一遍。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询