☰
MyBatis逆向工程实战:从generatorConfig到Spring整合全流程
2026/10/10 21:13:34 网站建设 项目流程

干 Java 后端的人,十有八九都手写过 Mapper.xml。表少的时候没什么,可一旦业务表过二十张,重复的 CRUD 代码能把人写吐。我自己就经历过夜里十一点还在补 insert 语句的窘境,后来才认真把 MyBatis 逆向工程用起来,配合 Spring 做完全自动化的整合,半年下来维护成本降了一大截。这篇笔记就是把整套流程沉淀下来,从 generatorConfig.xml 的每个参数,到 Spring 里如何扫描装配,再到实际工作中踩过的坑,一次讲透。

这篇笔记适合谁?刚接触 MyBatis Generator 的初学者,能从零搭起一套可用的生成链路;已经在用但经常被配置折腾的老手,可以对比我的参数取舍和问题排查思路;需要给团队写开发规范的组长,也能直接把我这套方案当模板。不管你是传统 Spring XML 工程还是 Spring Boot 工程,核心思路都通用。

1. 逆向工程的设计思路:先看清它到底替你干了什么活

1.1 逆向工程的核心价值与适用边界

MyBatis 逆向工程(MyBatis Generator,下文简称 MBG)做的事情很简单:连接数据库,读取表结构,然后帮你生成实体类、Mapper 接口、Mapper XML 文件和 Example 类。很多人把它当成“懒人工具”,但用过一段时间后你会发现,它真正的价值不是省那几分钟敲代码的时间,而是统一了团队的编码规范。

举个例子。你们团队五个人,每个人写查询条件都有自己的习惯——有人喜欢拼 String,有人喜欢用 Map 传参,有人直接在 XML 里写 OGNL 表达式判断。时间一长,Mapper 层就乱了。MBG 生成的代码是高度模板化的,所有单表操作都走同一套风格:Example 类负责动态条件,BaseResultMap 负责结果映射,Base_Column_List 负责列名常量。新成员看一眼生成代码就能上手,Review 代码时也只用关注业务逻辑,不用纠结底层写法。

但 MBG 不是万能的。它的能力边界非常明确:只处理单表的 CRUD 和动态查询。多表关联、复杂子查询、存储过程调用、批量插入,这些还是要手写。所以我的建议是:把 MBG 定位成“基础代码生成器”,生成的东西是起点,不是终点。手写 XML 时,直接在生成的 Mapper 里加自定义方法,完全没问题,不要因为“这是生成的文件”就不敢改。

1.2 两种 Spring 整合路径的选择逻辑

Spring 整合 MyBatis 逆向工程生成结果,本质上是两件事:一是让 Spring 容器管理 SqlSessionFactory,二是让 Mapper 接口能被自动扫描并注入到 Service 层。

传统 Spring XML 工程里,大多数人用的是SqlSessionFactoryBean+MapperScannerConfigurer的组合。前者读取 MyBatis 全局配置和 Mapper XML 文件,后者扫描指定包路径下的 Mapper 接口,自动生成代理对象注册到容器里。这套组合很稳,Spring 3 时代就这么用,到现在依然没毛病。

Spring Boot 工程则简单得多:引入mybatis-spring-boot-starter,配置mapper-locations指向 XML 文件路径,接口上加@Mapper注解或用@MapperScan扫包,完事。Boot 的自动配置类在内部还是走了SqlSessionFactoryBean那套逻辑,只是把 XML 里的 Bean 声明换成了 Java Config。

两种路径选哪个,我的判断标准很简单:你的团队对 Spring Boot 的熟悉程度。如果还在维护老项目,统一走 XML 方案,跟现有代码风格一致;如果是新项目,直接上 Boot,别犹豫。

1.3 版本选型:这步定不好后面全是坑

版本问题是我踩过最深的坑,必须单独拿出来说。MBG 从 1.4.0 版本开始有 breaking change,commentGenerator的配置方式变了,targetRuntime的枚举值也有调整。如果你网上搜到一篇老教程,照抄配置在 1.4.x 上跑,大概率会报错。

我当前的建议组合:

组件建议版本备注
MyBatis3.5.x3.5.9 以后的版本对 JDK 8/11/17 都友好
MBG1.4.1稳定,配置方式跟新教程能对上
mybatis-spring2.1.x配合 Spring 5.x 使用时建议 2.1.2 以上
Spring Framework5.3.x 或 6.x按项目现状定,MBG 这边不冲突
MySQL Connector/J8.0.x注意驱动类名是com.mysql.cj.jdbc.Driver

另外要确认你的mybatis-generator-maven-plugin插件版本和mybatis-generator-core依赖版本一致,不一致的话会出现“插件报错但代码已生成”这种诡异情况。

2. generatorConfig.xml 配置逐项拆解:每个参数都是什么、为什么

2.1 配置文件的整体骨架

MBG 的核心配置文件是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> <context id="mysqlContext" targetRuntime="MyBatis3Simple" defaultModelType="flat"> <property name="javaFileEncoding" value="UTF-8"/> <property name="beginningDelimiter" value="`"/> <property name="endingDelimiter" value="`"/> <commentGenerator> <property name="suppressAllComments" value="true"/> </commentGenerator> <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" userId="root" password="your_password"> </jdbcConnection> <javaTypeResolver> <property name="forceBigDecimals" value="false"/> </javaTypeResolver> <javaModelGenerator targetPackage="com.example.model" targetProject="src/main/java"> <property name="enableSubPackages" value="true"/> <property name="trimStrings" value="true"/> </javaModelGenerator> <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"> <property name="enableSubPackages" value="true"/> </sqlMapGenerator> <javaClientGenerator type="XMLMAPPER" targetPackage="com.example.mapper" targetProject="src/main/java"> <property name="enableSubPackages" value="true"/> </javaClientGenerator> <table tableName="user" domainObjectName="User" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"/> </context> </generatorConfiguration>

这段配置里有个细节值得注意:targetRuntime我填的是MyBatis3Simple,这个模式下生成的是纯 CRUD,没有 Example 类。如果你的业务大量依赖动态查询,改成MyBatis3,Example 类就有了。两个模式生成的代码风格差别很大,我后面会细讲怎么选。

2.2 context 层参数的逻辑与取舍

context标签的defaultModelType有三个可选值:flat、hierarchical、conditional。默认为conditional,但我建议显式指定flat。这个参数决定实体类的组织方式:hierarchical会把主键类、BLOB 类、普通字段类分开生成;flat则所有字段集中到一个类里。对大多数业务场景来说,一个表对应一个实体类就够了,分开生成反而让包结构变得繁琐。

beginningDelimiter和endingDelimiter必须配置。MySQL 的表名和字段名有时候会用到保留字,比如order、group、desc。如果不加反引号包裹,生成的 SQL 脚本执行时直接报语法错误。这两个属性就是告诉 MBG:生成 SQL 时用反引号把表名、字段名括起来。

javaFileEncoding设为 UTF-8 也是必须的。很多人不注意这个,默认用系统编码,Windows 环境下生成的中文注释全部乱码。虽然我建议关掉注释,但万一你要保留注释,编码不对就是灾难。

2.3 commentGenerator:为什么要关掉注释

这是一个争议点,我说说自己的看法。commentGenerator负责给生成文件添加注释,默认生成的注释含有 “This class is auto generated by MyBatis Generator” 字样和表字段的备注信息。

我的做法是suppressAllComments设为true,全部关掉。原因有三:第一,生成的注释会带着生成时间戳,代码进了 Git 之后每次重新生成都会产生大量 diff,Review 的人看得头大;第二,很多 IDE 会在你格式化代码时把注释重新排版,每次生成后都要处理;第三,表字段的备注信息在实体类上的价值很小,字段名已经足够表达含义,真正需要的是在数据库文档里维护。

如果你有合规要求,必须保留 “此文件由 MBG 生成” 的声明,那也可以只关时间戳,保留注释。1.4.0 之后可以通过自定义commentGenerator来实现,但我觉得大部分人用不上。

2.4 JDBC 连接与类型解析器:从表字段到 Java 类型的翻译规则

jdbcConnection配置简单,但有几个容易踩雷的细节。MySQL 8.0 驱动类名是com.mysql.cj.jdbc.Driver,不是com.mysql.jdbc.Driver,后者在 8.x 驱动里已经移除了。连接串里必须带上serverTimezone=Asia/Shanghai,否则驱动会抱怨时区无法识别。useSSL=false是因为本地开发一般不需要 SSL,加上反而可能有证书警告。

javaTypeResolver控制 JDBC 类型到 Java 类型的映射。默认情况下,TINYINT 映射到 Byte,INTEGER 映射到 Integer,BIGINT 映射到 Long,DECIMAL 映射到 BigDecimal。forceBigDecimals设为false时,DECIMAL 会根据精度判断——精度高才转 BigDecimal,精度低转 Double。如果你涉及金额计算且不希望出现浮点数精度问题,把forceBigDecimals改成true,所有 DECIMAL 都走 BigDecimal。

这里有个很隐蔽的问题:数据库里的BIT(1)类型,MBG 默认生成Boolean,但如果你的业务层用Integer(有的框架对 Boolean 处理不友好),就需要在表字段上配置<columnOverride>手动指定。我见过好几个同事在这上面栽跟头,生成之后查不到数据,最后发现是类型映射偏差。

2.5 table 标签:控制生成范围与 Example 类的开关

table标签是配置的收尾部分,也是工作量最大的部分。每张表都要写一行<table>。三个关键属性要说明:

tableName是数据库表名,domainObjectName是生成的实体类名。很多人不写domainObjectName,让 MBG 自己从表名推断。对于t_user、sys_role这类带前缀的表,自动推断会得到TUser、SysRole,类名里带大写字母前缀,看着别扭。我一般手动指定。

<table tableName="t_user" domainObjectName="User"/> <table tableName="t_order_item" domainObjectName="OrderItem"/>

四个 Example 相关开关参数——enableCountByExample、enableUpdateByExample、enableDeleteByExample、enableSelectByExample——对应是否生成 Example 类的方法。举个例子:你配置了targetRuntime="MyBatis3",默认生成完整 CRUD 加 Example 方法,但你的业务其实只用selectByPrimaryKey和selectByExample,那count/update/delete三个 Example 方法都是垃圾代码,每张表代码量多出 20%,全是不用的。

我的推荐组合是:如果表是只读的查询表,全部 Example 关掉,保留基础 CRUD;如果是核心业务表,保留selectByExample和countByExample,关掉updateByExample和deleteByExample。updateByExample容易误操作——没有条件的 update 会把整张表更新掉,生产环境出过一次事故后,我对这个方法深恶痛绝。

2.6 实战决策:targetRuntime 选 MyBatis3 还是 MyBatis3Simple

这个问题几乎每场培训都会被问到。直接给建议:

MyBatis3Simple生成的代码干净,只有deleteByPrimaryKey、insert、selectByPrimaryKey、updateByPrimaryKey和selectAll,完全没有动态条件判断的 XML。如果你的查询都写在自定义 XML 里,不需要 Example 类,选这个。

MyBatis3会额外生成 Example 类和对应的查询方法,XML 里的动态 SQL 也多了不少。当你需要根据多条件组合查询时,Example 能省很多事,不用自己写<where><if>拼条件。代价是生成代码量大,而且 Example 类的方法链写法学习成本有点高。

我的真实体验是:新项目优先用MyBatis3Simple,需要动态查询时手写 XML 方法。原因很简单——Example 类生成的动态 SQL 可读性差,方法链一长基本没人愿意看,而手写 Query 条件类在可读性和灵活性上更可控。直接用 Simple 模式,至少不会给团队留一个“反正有 Example,以后就都这么查”的坑。

3. Spring 整合实操:从零搭好一条完整的生成链路

3.1 Maven 插件配置与依赖准备

Maven 是执行 MBG 最舒服的方式,不需要额外装客户端。在pom.xml里加插件依赖,其他什么都不用动。

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

configurationFile指定配置文件位置,我习惯放在src/main/resources下,跟 Spring 配置放一起,方便统一管理。overwrite设为true,表示重新生成时覆盖同名文件。这里注意,MBG 的覆盖策略比较特殊:XML 文件会整体覆盖,Java 文件会根据内容差异选择是否重新写入。如果你改过生成的 Java 文件,MBG 对比内容后不同,会保留你的版本,控制台打印“不覆盖”的提示。这个机制后面我详细讲。

插件依赖里必须带 MySQL 驱动包。很多新手在这里翻车,只配了插件,没加驱动依赖,运行时报找不到驱动类。

3.2 执行生成的三种方式

第一种,Maven 命令行:

mvn mybatis-generator:generate

这是最常用的方式。执行前确保generatorConfig.xml里的数据库 IP、账户、密码是对的,而且网络能通到数据库。

第二种,IDEA 的 Maven 面板:展开 Plugins,找到mybatis-generator,直接双击mybatis-generator:generate。效果跟命令行一样,只是省得敲命令。

第三种,Java 代码直接调 MBG。这种适合想把生成动作集成到 CI 或自定义工具链的场景:

List<String> warnings = new ArrayList<>(); ConfigurationParser cp = new ConfigurationParser(warnings); Configuration config = cp.parseConfiguration( Resources.getResourceAsStream("generatorConfig.xml")); DefaultShellCallback callback = new DefaultShellCallback(true); MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings); myBatisGenerator.generate(null); for (String warning : warnings) { System.out.println("警告: " + warning); }

第三种方式比较少见,但如果你在多模块工程里需要批量生成多个库的表,写个 Java 工具类跑一圈比逐个配 Maven 插件省事得多。

3.3 Spring XML 工程整合 SqlSessionFactoryBean 与 MapperScannerConfigurer

生成完代码后,下一步是让 Spring 管理它们。先看传统 Spring XML 工程的装配方式,在applicationContext.xml里配置两个 Bean:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean>

SqlSessionFactoryBean是整合的核心,它做了两件事:读取mybatis-config.xml里的全局配置(比如驼峰映射、缓存开关),同时加载mapperLocations指定的所有 XML 文件。这两件事缺一不可——如果只配置了 XML 的扫描,MyBatis 的全局设置就丢了;如果只配置了全局设置,Mapper XML 找不到,运行时报Invalid bound statement。

MapperScannerConfigurer里面的sqlSessionFactoryBeanName用字符串引用 Bean 的名字,而不是ref直接引用。这是一个细节:如果直接ref引用,Spring 会提前初始化sqlSessionFactory,导致配置里的属性还没完全注入就用了。用BeanName避免循环依赖问题,属于老经验了,但到现在依然有效。

mybatis-config.xml本身也很简单,如果你不需要特殊配置,里面只放个settings就够了:

<configuration> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> <setting name="logImpl" value="SLF4J"/> </settings> <typeAliases> <package name="com.example.model"/> </typeAliases> </configuration>

mapUnderscoreToCamelCase强烈建议打开。MBG 生成的 XML 默认用resultMap做映射,列名和属性名是一一对应的。但你自己手写的 SQL 方法,如果返回类型是实体类,数据库下划线列名user_name和实体属性userName之间如果没有这个配置,查询结果就是 null。打开驼峰映射后,手写 SQL 就能少写很多as别名。

3.4 Spring Boot 工程的整合差异

Spring Boot 的整合几乎是把上面 XML 里的配置搬到application.yml:

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/your_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.model configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl

入口类上加个@MapperScan("com.example.mapper"),或者在每个 Mapper 接口上加@Mapper注解,二选一即可。我推荐用@MapperScan,一个注解搞定所有接口,省得每个接口都要加注解。

Boot 版本如果用的 3.x,对应mybatis-spring-boot-starter也要换成 3.x 版本,底层才兼容 Spring Framework 6 和 JDK 17。这个对应关系不难记:Boot 2.x 配 starter 2.x,Boot 3.x 配 starter 3.x。

3.5 生成后的代码检查清单

跑完生成和 Spring 整合后,别急着写业务。先过一遍检查清单:

  1. 实体类的字段类型是否符合预期。重点检查 BigDecimal、Date 类型,看有没有因为javaTypeResolver配置不当导致类型偏差。
  2. Mapper XML 里的resultMap是否完整。如果表字段很多,扫一眼有没有漏掉的。
  3. 每个 Mapper 接口是否都有配套 XML。MBG 的 XMLMAPPER 模式下,接口方法如果有@Select注解就不需要 XML,否则一定对应 XML 里的某个id。
  4. 项目启动时看日志,有没有 Mapper XML 解析失败、绑定异常的信息。启动阶段的报错大多集中在这里。
  5. 跑一个最简单的selectByPrimaryKey,看返回结果。这一步能最快验证整个链路是否通了。

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

4.1 问题速查表:我踩过的坑都在这里

我把实际工作中见过的问题整理成一张表,方便你直接对照排查:

现象原因解决办法
运行插件报Failed to execute goal,提示找不到驱动类插件没有依赖 MySQL 驱动在插件的 dependencies 里加mysql-connector-j
生成后中文注释乱码javaFileEncoding没设 UTF-8在context下配置 propertyjavaFileEncoding 为 UTF-8
Spring 启动时报Invalid bound statement (not found)Mapper 接口和 XML 文件没有绑定检查mapper-locations路径是否正确,XML 命名空间是否等于接口全限定名
启动报NoSuchBeanDefinitionException,Mapper 接口没注入漏了@MapperScan或 MapperScannerConfigurer确认扫描包路径包含所有 Mapper 接口
查询返回字段全是 null,但 SQL 执行有结果没开mapUnderscoreToCamelCase在全局配置打开驼峰映射
生成的实体类没有toString、equals等方法MBG 默认不生成,尤其MyBatis3Simple模式需要的话用 Lombok@Data,或者手动补
updateByExample生成的 SQL 把全表更新了Example 参数为空,条件未生效业务层强制校验 Example 条件,或直接关掉该方法生成
BLOB/TEXT 字段生成到实体类里又大又占内存MBG 默认把longtext映射成 String用columnOverride指定javaType,或者单独建视图表抽离 BLOB

4.2 MyBatis 3.5.x 与 MBG 1.4.x 的兼容性细节

MBG 1.4.x 运行时有个现象:每次生成都会打印一条 aboutCannot connect to database警告,然后执行成功。别慌,这通常不是数据库连不上,而是 MBG 在初始化时尝试探测数据库方言的兼容测试。我排查过好几次,确认生成的代码没问题,就无视这条警告。

另外,MBG 1.4.x 删除了一些 1.3.x 的旧属性。比如enableDeleteByExample、enableUpdateByExample这些开关,在 1.4.x 里依然保留,语义不变;但enableInsert、enableSelectByPrimaryKey这类开关,在 1.4.x 中已经失效,控制不了方法生成。如果你想要精细控制生成哪些方法,建议生成后用MyBatis3Simple这种精简模式,而不是费劲去研究已经失效的开关。

4.3 覆盖策略:为什么 Java 文件总是“生成不了”

MBG 的覆盖策略容易让人困惑。overwrite=true并不是说所有文件都会无条件覆盖。它的实际逻辑分两种:

XML 文件(sqlMapGenerator生成的部分):生成策略是整体覆盖,不管你是否改动过,重新生成后 XML 内容跟配置完全一致。

Java 文件(实体类、Mapper 接口):MBG 会解析现有文件的 AST(抽象语法树),对比要生成的内容。如果现有文件里有 MBG 不会生成的代码(比如你手写的方法),MBG 会保留你的内容,只更新它自己生成的部分。但如果现有文件跟生成内容完全一致,就直接覆盖写回。

这意味着一个实际场景:你在生成的UserMapper.java里加了一个自定义方法findByName,重新跑 MBG,这个自定义方法会被保留,不会被删掉。但如果你在User.java里手动加了一个字段tempField,MBG 检测到这不是它生成的,也会保留。这个机制对增量开发很友好,但它有一个副产物——手改过的文件会出现混编状态,时间久了文件里既有生成代码又有手写代码,代码风格会被打乱。

我的建议是:生成的 Java 文件原则上不手改,需要扩展逻辑就新建类,或者直接在 Mapper XML 里加自定义 SQL。如果确实需要改,把类名上方的 “Generated by MBG” 注释保留住,每次生成后手动确认 diff。

4.4 Example 类滥用导致的全表更新事故

这个事故我必须要写进笔记里,因为它的教训太深刻了。

场景是这样的:同事写了一个方法,根据用户 ID 更新用户状态,他用了updateByExample,大概长这样:

UserExample example = new UserExample(); example.createCriteria().andIdEqualTo(userId); userMapper.updateByExample(user, example);

代码没问题,但有一次他把example.createCriteria()那行漏了,直接写成了:

userMapper.updateByExample(user, example);

Example 为空对象,MBG 生成的动态 update SQL 就会变成update user set ...,不带任何 where 条件。结果就是全表用户状态被更新。

这个事故暴露的不是代码逻辑问题,而是updateByExample这个 API 本身太危险。它在设计上允许你传入空 Example 来执行全表更新,但没有任何安全护栏。

从那之后,我在团队规范里定了两条:一是生成配置里直接关掉updateByExample和deleteByExample;二是自定义更新的 SQL 必须手写,并显式带上where条件。宁可多写几行代码,也不要留给未来一个“一键清空全表”的危险开关。

4.5 Lombok 与生成代码的共存姿势

Lombok 现在基本是 Java 项目的标配,MBG 默认生成的是 getter/setter,用 Lombok 就会重复、冗余。处理方式有两条路:

第一条:MBG 生成 getter/setter,不引入 Lombok。代码多一些,但对生成工具兼容性最好,不会有任何字节码层面的冲突。

第二条:用 Lombok 的@Data注解替换 getter/setter,需要在配置里自定义commentGenerator的addFieldComment和addClassComment方法,在实体类上加上注解。这样生成出来的实体类只有字段和@Data,非常干净。

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

自定义commentGenerator类,核心逻辑大概是:

public class MyCommentGenerator extends DefaultCommentGenerator { @Override public void addModelClassComment(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) { // 在类上添加 @Data 注解 topLevelClass.addAnnotation("@Data"); // 给所有字段加 @TableField 之类的注解(如果用到 MyBatis-Plus) } }

注意:自定义CommentGenerator的类要打包到插件依赖里,不然 Maven 插件运行时会报ClassNotFound。

我在实际项目里的做法是:自定义CommentGenerator,实体类统一加@Data,Mapper 接口不生成任何注解,完全靠 Spring 扫包。这样做代码量最省,团队统一规则,IDE 补全也舒服。

4.6 多模块工程下的路径配置

不要上来就无脑把 Maven 模块结构复杂化,先理解 MBG 的targetProject是相对路径还是绝对路径。

targetProject支持相对路径,但相对于哪个目录,取决于你从哪里执行 Maven 命令。多模块工程里,如果你在模块 A 的目录下执行mvn mybatis-generator:generate,那targetProject="src/main/java"就解析到模块 A 的src/main/java。这个行为在 IDE 里和命令行里可能不一样,因为 IDE 的执行目录有时候是根项目目录,所以最好在配置里显式写绝对路径,或者用变量。

我的方案:在pom.xml里通过 Maven Properties 把路径传进generatorConfig.xml。比如:

<properties> <generator.model.project>${project.basedir}/src/main/java</generator.model.project> </properties>

然后在generatorConfig.xml里引用:

<javaModelGenerator targetPackage="com.example.model" targetProject="${generator.model.project}">

这样无论从哪里执行,路径都是以模块的basedir为基准,不会出现生成到文件夹外的情况。

如果你是多模块工程,比如common模块放实体类和 Mapper,service模块放业务代码,MBG 生成时需要指定到common模块的 Java 目录和资源目录。我见过很多人配置半天生成到根目录,一运行直接报文件已存在,都是路径没搞对。

4.7 自定义类型处理器:当 MBG 的默认映射不够用

前面提到类型映射问题,这里展开讲一个更进阶的场景:当你需要在实体类里存 JSON 字段时。

MySQL 5.7 以后支持 JSON 类型,但 MBG 的默认映射里,JSON 类型没有对应的 Java 类型,默认映射成 String,然后在 MyBatis 里用一个 TypeHandler 把 JSON 转成对象。

操作分三步:

第一步,在表配置里指定columnOverride:

<table tableName="user_profile"> <columnOverride column="extra_info" javaType="com.example.model.UserExtraInfo" typeHandler="com.example.handler.JsonTypeHandler"/> </table>

第二步,写一个 TypeHandler:

@MappedTypes(UserExtraInfo.class) @MappedJdbcTypes(JdbcType.VARCHAR) public class JsonTypeHandler extends BaseTypeHandler<UserExtraInfo> { private static final ObjectMapper MAPPER = new ObjectMapper(); @Override public void setNonNullParameter(PreparedStatement ps, int i, UserExtraInfo parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, MAPPER.writeValueAsString(parameter)); } @Override public UserExtraInfo getNullableResult(ResultSet rs, String columnName) throws SQLException { return MAPPER.readValue(rs.getString(columnName), UserExtraInfo.class); } // 其他两个重写方法类似,这里省略 }

第三步,手写一个查询方法调用这个字段的时候,MyBatis 会自动应用columnOverride里配置的typeHandler。

这个方案让我在业务层直接面向对象操作 JSON 字段,不用每个 Service 里手动序列化反序列化。但注意一点:resultMap的生成是在执行select时用的,如果你手写 SQL 返回user_profile表的字段,要在 SQL 里用#{}传参,MyBatis 才能识别到 typeHandler。

4.8 批量操作的逆向工程配合方案

上一节是高级用法,最后再分享一个“批量操作”的现实经验。

MBG 生成的默认 SQL 是单条 insert、单条 update,不支持批量。但实际开发里,批量插入是绕不开的需求。常见做法有两种:

第一种:在 Spring Boot 里用 MyBatis 的ExecutorType.BATCH。这种方式要在application.yml里配置 batch 执行器,然后在 Service 层用 SqlSessionTemplate 手动控制:

@Autowired private SqlSessionTemplate sqlSessionTemplate; public void batchInsert(List<User> users) { UserMapper batchMapper = sqlSessionTemplate.getMapper(UserMapper.class); for (User user : users) { batchMapper.insert(user); } sqlSessionTemplate.flushStatements(); }

但注意:SqlSessionTemplate的flushStatements()行为常常让人迷惑,而且ExecutorType.BATCH模式下,一级缓存里保存的 statement 不会立即执行,如果你在同一个事务里接着查询,可能查不到刚插入的数据。所以这个方案要处理缓存刷新问题。

第二种:直接在 Mapper XML 里手写foreach批量 insert:

<insert id="batchInsert"> insert into user (id, name, age, created_at) values <foreach collection="list" item="item" separator=","> (#{item.id}, #{item.name}, #{item.age}, #{item.createdAt}) </foreach> </insert>

这个方法简单直接,不用动 Executor 的全局配置,性能也足够应付大部分场景。注意foreach里的collection必须是list或collection对应的名称,根据 Mapper 接口的参数定义来定。如果你传的参数是List且没加@Param注解,MyBatis 默认的 key 就是list,加了@Param("users")就用users。

我实际项目里用的是第二种方案,理由很简单:它不依赖全局执行器类型,不会影响其他单条操作的执行行为,而且 SQL 直观、可调优。

5. 最后的实战建议

这篇笔记内容不少,但如果你只记住三件事,那我希望是这三条:

第一,MBG 是提升效率、统一规范的工具,不是代码质量的救世主。生成的代码一定要过一遍,尤其是类型映射和字段缺失的地方。

第二,updateByExample和deleteByExample这种危险方法,建议直接从生成配置里关掉。不要赌团队的每个人都足够小心。

第三,版本选择永远是第一位的。MBG 1.4.x 和 1.3.x 的配置差异不小,搜索资料时先确认版本再照搬,能省掉很多无意义的排查时间。

我在实际维护项目的过程中最大的体会是:逆向工程的价值,只有在跟进维护阶段才会真正显现。表结构变更时,改完数据库 DDL,跑一遍 MBG,实体类和 Mapper 马上就同步了。没有这套机制之前,每次加个字段都要手动改实体类、改 Mapper XML、改 Service 层的新增和更新逻辑,效率低还容易遗漏。现在团队里这个习惯已经固化了:改表必跑生成器,跑完必跑一遍全量编译和冒烟测试。

最后再分享一个小技巧。我在每张生成表的domainObjectName命名上有个习惯:数据库表名带前缀的,实体类一律去掉前缀;表名是单数还是复数,实体类也保持单数。比如t_user生成User,t_order_items生成OrderItem。别小看命名对齐这件事,等你的工程到了几十个实体类的时候,命名统一就是最大的效率保障。

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

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

立即咨询