news 2026/10/15 16:20:58

MyBatis逆向工程实战:MBG配置详解与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatis逆向工程实战:MBG配置详解与避坑指南

1. 为什么还要聊 MyBatis 逆向工具

做 Java 后端的人,几乎都绕不开持久层框架。MyBatis 因为 SQL 可控、映射灵活,在国内中小团队里一直是主流选择。但灵活是有代价的——每加一张表,就得手写一个实体类、一个 Mapper 接口、一个 XML 映射文件,字段一多,光是对齐列名和 Java 属性名就能耗掉小半天。表结构一改,还得回头挨个同步,漏一个字段就是线上空指针。

MyBatis-Generator(后面简称 MBG)就是来解决这个重复劳动的。它的核心逻辑很朴素:连上数据库,读取表结构元数据,然后按照你给的模板和规则,把实体类、Mapper 接口、XML 文件甚至 Example 查询条件类一次性生成出来。你只需要维护表结构,剩下的样板代码交给它。

这篇文章适合三类人看:一是刚接触 MyBatis、还在手写 CRUD 的同学,想找个能立刻上手的自动化方案;二是团队里负责搭脚手架的人,需要一套可复用、可定制的生成配置;三是已经用过 MBG 但总觉得生成结果“不太对味”、想深入定制的人。我会从整体设计思路讲到具体配置,再到踩过的坑,尽量把每个参数为什么这么设都说清楚。

需要先说明一点:MBG 的版本迭代比较快,不同版本对配置项的支持有差异。我下面讲的内容基于常见的 1.4.x 系列实践,如果你用的是更早或更晚的版本,个别标签名可能对不上,以官方文档为准。另外,本文所有示例中的库名、表名、包名都是虚构代称,你替换成自己的即可。

2. 整体设计思路与方案选型

2.1 逆向生成到底解决了什么问题

先说清楚“逆向”这个词。正常开发流程是先写 Java 代码、再建表,叫正向;逆向就是反过来,先有数据库表,再倒推出 Java 代码。MBG 干的就是后者。

它解决的问题可以拆成三层。第一层是消除重复劳动:一张 20 个字段的表,手写实体类加 getter/setter 就是 100 多行,Mapper XML 里的 resultMap 又是几十行,这些代码没有任何业务逻辑,纯属体力活。第二层是保证一致性:手写容易把user_name映射成userNmae,或者类型对不上,生成器按元数据来,不会犯这种错。第三层是降低维护成本:表加字段后重新生成一次,比人工比对快得多。

但要注意,MBG 不是银弹。它生成的是“能跑”的代码,不是“好”的代码。比如它默认生成的 Example 类,在复杂查询场景下会显得笨重,很多团队会选择关掉它,改用自定义 SQL。这个取舍后面会细讲。

2.2 三种主流接入方式怎么选

MBG 的接入方式主要有三种,各有适用场景,我整理成表格方便对比。

接入方式配置载体适用场景优点缺点
Maven 插件pom.xml团队协作、CI 集成配置随项目走,一条命令生成改配置要动 pom,调试稍麻烦
Java 代码调用独立 main 方法需要动态逻辑、复杂定制灵活,可编程控制要写额外代码,不够直观
命令行generatorConfig.xml临时生成、脚本化简单直接参数传递不便,易出错

我个人的选择是:团队项目一律用 Maven 插件。原因很实际——配置写在 pom 里,新人拉下代码就知道怎么生成,不用口口相传;而且可以绑定到某个 phase,配合 CI 做校验。Java 代码调用适合那种需要根据环境动态切换数据库、或者生成后还要做二次处理的场景。命令行现在用得少了,除非是写一次性脚本。

2.3 生成策略:全量还是增量

这是很多人纠结的点。全量生成就是每次覆盖所有文件,简单粗暴;增量生成是只生成新增的表,已有文件不动。

我的经验是:实体类和 Mapper 接口可以全量覆盖,XML 文件要谨慎。因为 XML 里经常会手写一些自定义 SQL,全量覆盖会把你的心血冲掉。解决办法有两个:一是把自定义 SQL 放到单独的 XML 文件里,生成的文件只管基础 CRUD;二是用 MBG 的@mbg.generated注释配合合并策略,但配置起来比较绕。

更稳妥的做法是:生成前先提交代码,生成后用 git diff 看一眼,确认没有覆盖掉不该覆盖的内容。这个习惯能救命。

3. 核心配置项逐个拆解

3.1 generatorConfig.xml 的骨架结构

MBG 的配置文件是一个 XML,根节点是<generatorConfiguration>,下面主要分三块:<properties>用于引入外部属性文件,<context>是核心配置容器,<commentGenerator>控制注释生成。一个典型的骨架长这样:

<?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> <properties resource="generator.properties"/> <context id="defaultContext" targetRuntime="MyBatis3"> <commentGenerator> <property name="suppressDate" value="true"/> <property name="addRemarkComments" value="true"/> </commentGenerator> <jdbcConnection driverClass="${jdbc.driver}" connectionURL="${jdbc.url}" userId="${jdbc.username}" password="${jdbc.password}"/> <javaTypeResolver> <property name="forceBigDecimals" value="false"/> </javaTypeResolver> <javaModelGenerator targetPackage="com.example.model" targetProject="src/main/java"/> <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"/> <javaClientGenerator type="XMLMAPPER" targetPackage="com.example.mapper" targetProject="src/main/java"/> <table tableName="user_info" domainObjectName="UserInfo"/> </context> </generatorConfiguration>

这里有几个点值得展开。targetRuntime有两个常用值:MyBatis3和MyBatis3Simple。前者会生成 Example 类和带条件的查询方法,后者只生成最基础的 CRUD,代码干净很多。如果你团队不用 Example,直接选MyBatis3Simple,能省掉一大堆冗余代码。

<properties>引入外部文件是为了把数据库连接信息抽出来,避免明文写在 XML 里提交到仓库。这个习惯一定要养成,密码泄露的教训太多了。

3.2 jdbcConnection 的连接细节

<jdbcConnection>负责连数据库读元数据。这里最容易出问题的是驱动类和连接串。

驱动类方面,MySQL 8.x 要用com.mysql.cj.jdbc.Driver,5.x 用com.mysql.jdbc.Driver,写错了会报 ClassNotFound。连接串建议加上useInformationSchema=true,这样 MBG 读注释信息会更准确。另外nullCatalogMeansCurrent=true这个参数在 MySQL 8 驱动下很有用,能避免它去扫描所有库导致生成变慢。

还有一个坑:如果数据库账号没有读取information_schema的权限,MBG 会读不到表注释,生成的字段注释就是空的。生产库通常权限收紧,建议用一个只读的、有元数据权限的账号来跑生成。

3.3 javaTypeResolver 的类型映射

数据库类型到 Java 类型的映射,由<javaTypeResolver>控制。默认情况下,forceBigDecimals是 false,意思是 DECIMAL 和 NUMERIC 类型如果精度为 0,会映射成 Integer 或 Long;设为 true 则一律映射成 BigDecimal。

这个选择要看业务。金额字段必须用 BigDecimal,精度不能丢,所以涉及钱的表建议开 true。但如果是那种“数量”字段用 DECIMAL 存的,映射成 Integer 更顺手。我的做法是全局设 false,然后在具体表的<columnOverride>里对金额字段单独指定javaType="java.math.BigDecimal",精准控制。

3.4 三个 Generator 的职责划分

javaModelGenerator管实体类,sqlMapGenerator管 XML,javaClientGenerator管 Mapper 接口。它们的targetProject路径写法有讲究。

如果用 Maven 插件,路径是相对于项目根目录的,比如src/main/java。如果用 Java 代码调用,路径是相对于当前工作目录的。很多人第一次跑报“targetProject does not exist”,八成是路径写错了。建议用绝对路径或者确认好相对基准。

javaClientGenerator的type属性,XMLMAPPER表示接口和 XML 分离,ANNOTATEDMAPPER表示 SQL 用注解写在接口上。后者生成的代码更紧凑,但复杂 SQL 用注解写会很难看,所以实际项目里XMLMAPPER是绝对主流。

4. 实操过程与关键环节

4.1 Maven 插件的完整配置

把 MBG 集成到 Maven 里,核心是在 pom.xml 的<build><plugins>下加一段配置:

<plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.4.0</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.28</version> </dependency> </dependencies> </plugin>

注意<dependencies>这一块,必须把数据库驱动作为插件的依赖引入,而不是项目的依赖。因为插件运行在独立的类加载器里,项目依赖它看不到。这个点坑过很多人,报错信息通常是“Cannot load driver class”。

overwrite设为 true 表示覆盖已有文件,verbose设为 true 会打印详细日志,调试阶段建议开着。

配置好后,执行命令:

mvn mybatis-generator:generate

如果只想生成某张表,可以加参数-Dmybatis.generator.tableNames=user_info,配合配置文件里的通配符使用。

4.2 table 标签的精细控制

<table>标签是控制单表生成行为的核心,属性很多,我挑最常用的几个讲。

tableName支持通配符,比如tableName="%"表示所有表,tableName="t_%"表示 t_ 开头的表。但通配符用多了容易生成一堆不需要的代码,建议显式列出表名,或者用%配合ignoreColumn排除。

domainObjectName指定实体类名,不写的话 MBG 会把表名转成驼峰。比如user_info默认生成UserInfo,但如果你想要User,就得显式指定。

enableCountByExample、enableUpdateByExample、enableDeleteByExample、enableSelectByExample这四个开关控制是否生成 Example 相关方法。如果你用MyBatis3Simple,它们默认就是 false;如果用MyBatis3,建议按需关闭,只留selectByExample,其他关掉,代码会清爽很多。

<columnOverride>用来覆盖单个列的映射。比如某个字段数据库是tinyint(1),你想映射成 Boolean:

<table tableName="user_info" domainObjectName="UserInfo"> <columnOverride column="is_active" javaType="java.lang.Boolean"/> <ignoreColumn column="internal_flag"/> </table>

ignoreColumn可以排除不想生成的字段,比如一些内部标记位。

4.3 生成结果的结构解读

跑完生成后,你会得到三类文件。以user_info表为例:

实体类UserInfo.java里包含所有字段的私有属性、getter/setter、以及toString。如果开了 Example,还会有一个UserInfoExample.java,里面是各种 Criteria 条件构造器。

Mapper 接口UserInfoMapper.java里是方法声明,比如selectByPrimaryKey、insertSelective、updateByExampleSelective等。注意Selective后缀的方法,它只会更新非 null 字段,这个设计很实用,避免了把字段误更新成 null。

XML 文件UserInfoMapper.xml里是具体的 SQL 和 resultMap。resultMap 的 id 通常是BaseResultMap,还有一个Base_Column_List的 sql 片段,方便复用。

这里有个细节:insert和insertSelective的区别。前者插入所有字段,null 也会写进去;后者只插入非 null 字段,依赖数据库默认值。大部分场景用insertSelective更安全。

4.4 自定义注释生成器

MBG 默认生成的注释很简陋,就是字段名加类型。想要生成带表注释、字段注释的中文说明,需要自定义CommentGenerator。

实现方式是写一个类实现org.mybatis.generator.api.CommentGenerator接口,然后在配置里指定:

<commentGenerator type="com.example.MyCommentGenerator"> <property name="suppressDate" value="true"/> </commentGenerator>

自定义类里重写addFieldComment方法,从introspectedColumn.getRemarks()拿到数据库里的字段注释,拼接到 JavaDoc 里。这样生成的实体类每个字段上面都有中文说明,可读性提升一大截。

要注意的是,getRemarks()依赖数据库的 comment 信息,所以建表时一定要写COMMENT,否则拿到的就是空字符串。

5. 常见问题与排查技巧

5.1 生成报错速查表

我把这些年遇到的高频问题整理成表格,方便对照排查。

报错信息根本原因解决办法
Cannot load driver class驱动没加到插件依赖里在 plugin 的 dependencies 中引入驱动
targetProject does not exist路径写错或目录不存在检查相对路径基准,或改用绝对路径
Table configuration not found表名拼写错误或库选错确认连接串中的库名和表名
生成的中文注释乱码文件编码不一致统一设为 UTF-8,配置里加 encoding
重复生成导致方法冲突多次生成未清理旧文件生成前清理,或用 overwrite 覆盖
时间字段映射成 Timestamp默认类型解析规则用 columnOverride 指定 javaType

5.2 覆盖代码的预防措施

前面提过 XML 被覆盖的问题,这里给一个具体方案。把生成的文件和手写的文件物理隔离:

  • 生成的 XML 放在src/main/resources/mapper/generated/目录
  • 手写的 XML 放在src/main/resources/mapper/custom/目录
  • MyBatis 配置里两个目录都扫描

这样重新生成时,只会动 generated 目录,custom 目录安然无恙。Mapper 接口同理,生成的接口和自定义接口分开,自定义接口继承生成的接口,扩展方法写在子接口里。

这个分层思路虽然多了一层,但长期维护成本低得多。我见过太多项目因为生成覆盖导致自定义 SQL 丢失,最后不得不从 git 历史里捞代码。

5.3 Example 类的取舍经验

Example 类是个双刃剑。它的好处是能用 Java 代码拼查询条件,不用写 SQL,简单查询很方便。坏处是复杂查询(多表关联、子查询、动态排序)用 Example 写会非常别扭,而且生成的 Criteria 方法一大堆,IDE 里提示列表长得吓人。

我的建议是:简单单表查询用 Example,复杂查询一律手写 XML。如果团队约定不用 Example,直接在配置里把四个 enable 开关全关掉,选MyBatis3Simple,生成的代码量能减少一半以上。

还有一个隐藏问题:Example 的andXxxEqualTo方法在字段为 null 时行为不一致,有的版本会忽略,有的会生成is null条件。跨版本升级时要特别注意这个差异。

5.4 字段类型映射的坑

数据库的datetime默认映射成java.util.Date,但现在很多项目用java.time.LocalDateTime。MBG 默认不支持 JSR-310 类型,需要自己写JavaTypeResolver实现类,或者用columnOverride逐个指定。

tinyint(1)在 MySQL 里常用来存布尔值,但 MBG 默认映射成Byte,不是Boolean。这个也要用columnOverride处理,否则业务代码里得手动转换,很烦。

text和longtext默认映射成String,这个没问题。但如果是blob,会映射成byte[],实体类的toString方法打印出来是一串乱码,建议在自定义注释生成器里把这类字段排除掉。

6. 进阶定制与团队实践

6.1 用插件扩展生成逻辑

MBG 支持插件机制,可以在生成过程中插入自定义逻辑。实现org.mybatis.generator.api.PluginAdapter接口,重写感兴趣的方法。

常见的扩展场景有两个。一是给实体类加 Lombok 注解,省掉 getter/setter。做法是在modelBaseRecordClassGenerated方法里,往TopLevelClass添加@Data注解。二是给 Mapper 接口加自定义父接口,统一一些通用方法。

插件配置方式:

<plugin type="com.example.LombokPlugin"> <property name="someOption" value="xxx"/> </plugin>

插件写好后,生成出来的代码就带 Lombok 了,实体类能瘦身一大半。不过要注意,Lombok 注解处理器和 IDE 插件要装好,否则编译报错。

6.2 多模块项目的路径处理

在多模块 Maven 项目里,MBG 通常放在某个基础模块或者独立的 generator 模块。这时候targetProject的路径要指向目标模块,比如../user-service/src/main/java。

这种相对路径很脆弱,模块目录一改就失效。更稳的做法是用 Maven 属性占位符,在 pom 里定义${project.basedir}之类的变量,配置文件中引用。或者干脆把生成器做成独立模块,生成后手动拷贝,虽然土但可靠。

我现在的做法是:generator 模块单独存在,配置里用绝对路径指向各模块,通过 CI 定时跑或者手动触发。生成结果提交到仓库,代码评审时能看到 diff,避免意外覆盖。

6.3 生成代码要不要提交到仓库

这个问题争论很久,我的立场是:要提交。

理由有三。第一,CI 构建不应该依赖数据库连接,如果生成代码不提交,每次构建都得连库生成,环境依赖太重。第二,代码评审需要看到生成结果的变化,表结构改了,实体类跟着变,这个 diff 是有意义的。第三,新人拉下代码就能编译运行,不用先配数据库跑生成。

不提交的理由通常是“生成代码是衍生物,不该进版本库”。理论上对,但实践中带来的麻烦更多。折中方案是提交,但在.gitattributes里标记为 generated,评审时可以折叠。

6.4 与 Flyway 等迁移工具的配合

如果团队用 Flyway 或 Liquibase 管理表结构变更,MBG 可以和它们形成闭环:迁移脚本改表结构,MBG 重新生成代码,两者都提交。这样数据库和代码始终同步。

关键是要把生成步骤加到开发流程里。比如规定:任何涉及表结构的 PR,必须包含迁移脚本和重新生成的代码。CI 里可以加一个校验,跑一次生成,看是否有未提交的 diff,有就报错。这个检查能有效防止“改了表忘了改代码”的情况。

7. 我踩过的几个真实坑

第一个坑是驱动版本冲突。项目本身依赖 MySQL 5.x 驱动,插件里配了 8.x,结果生成时用的是插件类加载器里的 8.x,连接串却是 5.x 的格式,报了一堆莫名其妙的错。后来统一成 8.x,连接串加上时区参数serverTimezone=Asia/Shanghai才消停。

第二个坑是表名大小写。Linux 环境下 MySQL 默认表名区分大小写,本地 Windows 开发时表名是小写,生成配置里写了大写,本地能跑,CI 上就找不到表。后来规定表名一律小写下划线,配置里也统一小写,问题消失。

第三个坑是生成时机。有次在 feature 分支上跑生成,覆盖了主分支刚合并的自定义 SQL,因为那个分支还没 rebase。教训是:生成前先拉最新代码,生成后立刻 diff,确认无误再提交。

第四个坑是注释乱码。数据库是 UTF-8,但生成的文件是 GBK,中文注释全乱。解决办法是在 Maven 插件配置里加<configuration><encoding>UTF-8</encoding></configuration>,同时确保 IDE 的文件编码也是 UTF-8。

这些坑单看都不复杂,但凑在一起能折腾大半天。写出来是希望你别重复交学费。

8. 关于定制化的一点个人看法

MBG 这类工具的价值,在于把“确定性劳动”自动化,让人专注于“不确定性劳动”——也就是业务逻辑和架构设计。但它生成的代码终究是模板化的,不能指望它产出优雅的领域模型。

我的做法是:把生成代码当作“原材料”,在它之上做一层封装。比如生成的 Mapper 接口不直接暴露给 Service,而是包一层 Repository,把 Example 查询转换成更语义化的方法。这样既享受了自动化的效率,又保持了代码的可读性和可测试性。

另外,不要过度定制生成器。我见过有人把 MBG 改得面目全非,生成出来的代码确实符合团队规范,但升级版本时痛苦不堪,因为所有定制都要重新适配。定制要克制,只做那些收益明显、维护成本低的扩展,比如加 Lombok 注解、加中文注释。至于代码风格、命名规范这些,交给 IDE 的格式化工具和 Checkstyle 更合适。

工具是死的,流程是活的。把 MBG 嵌进你的开发流程,让它在该出现的时候自动出现,比把它当成一个需要精心伺候的宝贝要省心得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 16:20:52

高项随机3—5道英语题,要不要专门花时间学?

看到英文题&#xff0c;题干还没读&#xff0c;先点一个最长的选项。不少高项考生默认&#xff1a;“反正只有几道英语题&#xff0c;全靠运气就行。”高项综合知识包含75道单选题&#xff0c;其中通常随机出现3至5道英语题。题量不算大&#xff0c;确实不适合投入过多时间&…

作者头像 李华
网站建设 2026/10/15 16:20:11

蓝牙模块功耗优化实战:从参数调优到自有制造兜底能效

1. 从一块发烫的电池说起&#xff1a;蓝牙模块功耗优化的真实痛点做硬件产品的朋友大概率都经历过这样的场景&#xff1a;一款主打低功耗的蓝牙传感器&#xff0c;标称一颗纽扣电池能撑一年&#xff0c;结果用户拿到手用了不到两个月就提示电量低。返修回来一测&#xff0c;静态…

作者头像 李华
网站建设 2026/10/15 16:20:08

从零搭建无人自助拼豆系统:核心功能实现与部署实战指南

从零搭建无人自助拼豆系统&#xff1a;核心功能实现与部署实战指南 随着物联网和智能设备的普及&#xff0c;无人自助服务系统逐渐成为一种高效、便捷的解决方案。本文将围绕“无人自助拼豆系统”的核心功能实现与部署进行技术解析&#xff0c;结合已有系统资料与通用开发经验&…

作者头像 李华
网站建设 2026/10/15 16:20:06

Delphi FireDAC连接MySQL实战:字符集、连接池与SSL配置全解析

1. 项目概述&#xff1a;为什么一个“老派”数据库连接方案至今仍被反复追问Delphi FireDAC连接MySQL数据库——这八个字组合在一起&#xff0c;乍看像是一份来自2010年代的技术备忘录。但如果你最近在某高校实验室帮导师调试毕业设计系统、在某中小企业的ERP维护现场排查数据同…

作者头像 李华
网站建设 2026/10/15 16:19:35

低功耗蓝牙模块功耗优化:从通用模块到自有制造兜底能效的实战指南

蓝牙模块的功耗问题&#xff0c;几乎是每个做便携式硬件产品的人都会撞上的那堵墙。我最早接触这类需求是在一个电池供电的传感器项目上&#xff0c;当时用了一颗市面上很常见的蓝牙模块&#xff0c;标称待机电流几十微安&#xff0c;实测下来整机待机直接飙到毫安级别&#xf…

作者头像 李华