MyBatis逆向工程实战:从generatorConfig到Spring整合全流程
干 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 工程里大多数人用的是SqlSessionFactoryBeanMapperScannerConfigurer的组合。前者读取 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 changecommentGenerator的配置方式变了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 version1.0 encodingUTF-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 idmysqlContext targetRuntimeMyBatis3Simple defaultModelTypeflat property namejavaFileEncoding valueUTF-8/ property namebeginningDelimiter value/ property nameendingDelimiter value/ commentGenerator property namesuppressAllComments valuetrue/ /commentGenerator jdbcConnection driverClasscom.mysql.cj.jdbc.Driver connectionURLjdbc:mysql://localhost:3306/your_db?useUnicodetrueamp;characterEncodingutf8amp;useSSLfalseamp;serverTimezoneAsia/Shanghai userIdroot passwordyour_password /jdbcConnection javaTypeResolver property nameforceBigDecimals valuefalse/ /javaTypeResolver javaModelGenerator targetPackagecom.example.model targetProjectsrc/main/java property nameenableSubPackages valuetrue/ property nametrimStrings valuetrue/ /javaModelGenerator sqlMapGenerator targetPackagemapper targetProjectsrc/main/resources property nameenableSubPackages valuetrue/ /sqlMapGenerator javaClientGenerator typeXMLMAPPER targetPackagecom.example.mapper targetProjectsrc/main/java property nameenableSubPackages valuetrue/ /javaClientGenerator table tableNameuser domainObjectNameUser enableCountByExamplefalse enableUpdateByExamplefalse enableDeleteByExamplefalse enableSelectByExamplefalse selectByExampleQueryIdfalse/ /context /generatorConfiguration这段配置里有个细节值得注意targetRuntime我填的是MyBatis3Simple这个模式下生成的是纯 CRUD没有 Example 类。如果你的业务大量依赖动态查询改成MyBatis3Example 类就有了。两个模式生成的代码风格差别很大我后面会细讲怎么选。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 之后每次重新生成都会产生大量 diffReview 的人看得头大第二很多 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 驱动里已经移除了。连接串里必须带上serverTimezoneAsia/Shanghai否则驱动会抱怨时区无法识别。useSSLfalse是因为本地开发一般不需要 SSL加上反而可能有证书警告。javaTypeResolver控制 JDBC 类型到 Java 类型的映射。默认情况下TINYINT 映射到 ByteINTEGER 映射到 IntegerBIGINT 映射到 LongDECIMAL 映射到 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 tableNamet_user domainObjectNameUser/ table tableNamet_order_item domainObjectNameOrderItem/四个 Example 相关开关参数——enableCountByExample、enableUpdateByExample、enableDeleteByExample、enableSelectByExample——对应是否生成 Example 类的方法。举个例子你配置了targetRuntimeMyBatis3默认生成完整 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 能省很多事不用自己写whereif拼条件。代价是生成代码量大而且 Example 类的方法链写法学习成本有点高。我的真实体验是新项目优先用MyBatis3Simple需要动态查询时手写 XML 方法。原因很简单——Example 类生成的动态 SQL 可读性差方法链一长基本没人愿意看而手写 Query 条件类在可读性和灵活性上更可控。直接用 Simple 模式至少不会给团队留一个“反正有 Example以后就都这么查”的坑。3. Spring 整合实操从零搭好一条完整的生成链路3.1 Maven 插件配置与依赖准备Maven 是执行 MBG 最舒服的方式不需要额外装客户端。在pom.xml里加插件依赖其他什么都不用动。build plugins plugin groupIdorg.mybatis.generator/groupId artifactIdmybatis-generator-maven-plugin/artifactId version1.4.1/version configuration configurationFilesrc/main/resources/generatorConfig.xml/configurationFile overwritetrue/overwrite verbosetrue/verbose /configuration dependencies dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.0.33/version /dependency /dependencies /plugin /plugins /buildconfigurationFile指定配置文件位置我习惯放在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 或自定义工具链的场景ListString 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里配置两个 Beanbean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property nameconfigLocation valueclasspath:mybatis-config.xml/ property namemapperLocations valueclasspath:mapper/*.xml/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.example.mapper/ property namesqlSessionFactoryBeanName valuesqlSessionFactory/ /beanSqlSessionFactoryBean是整合的核心它做了两件事读取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 namemapUnderscoreToCamelCase valuetrue/ setting namelogImpl valueSLF4J/ /settings typeAliases package namecom.example.model/ /typeAliases /configurationmapUnderscoreToCamelCase强烈建议打开。MBG 生成的 XML 默认用resultMap做映射列名和属性名是一一对应的。但你自己手写的 SQL 方法如果返回类型是实体类数据库下划线列名user_name和实体属性userName之间如果没有这个配置查询结果就是 null。打开驼峰映射后手写 SQL 就能少写很多as别名。3.4 Spring Boot 工程的整合差异Spring Boot 的整合几乎是把上面 XML 里的配置搬到application.ymlspring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/your_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/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.xBoot 3.x 配 starter 3.x。3.5 生成后的代码检查清单跑完生成和 Spring 整合后别急着写业务。先过一遍检查清单实体类的字段类型是否符合预期。重点检查 BigDecimal、Date 类型看有没有因为javaTypeResolver配置不当导致类型偏差。Mapper XML 里的resultMap是否完整。如果表字段很多扫一眼有没有漏掉的。每个 Mapper 接口是否都有配套 XML。MBG 的 XMLMAPPER 模式下接口方法如果有Select注解就不需要 XML否则一定对应 XML 里的某个id。项目启动时看日志有没有 Mapper XML 解析失败、绑定异常的信息。启动阶段的报错大多集中在这里。跑一个最简单的selectByPrimaryKey看返回结果。这一步能最快验证整个链路是否通了。4. 常见问题与排查技巧实录4.1 问题速查表我踩过的坑都在这里我把实际工作中见过的问题整理成一张表方便你直接对照排查现象原因解决办法运行插件报Failed to execute goal提示找不到驱动类插件没有依赖 MySQL 驱动在插件的 dependencies 里加mysql-connector-j生成后中文注释乱码javaFileEncoding没设 UTF-8在context下配置 propertyjavaFileEncoding 为 UTF-8Spring 启动时报Invalid bound statement (not found)Mapper 接口和 XML 文件没有绑定检查mapper-locations路径是否正确XML 命名空间是否等于接口全限定名启动报NoSuchBeanDefinitionExceptionMapper 接口没注入漏了MapperScan或 MapperScannerConfigurer确认扫描包路径包含所有 Mapper 接口查询返回字段全是 null但 SQL 执行有结果没开mapUnderscoreToCamelCase在全局配置打开驼峰映射生成的实体类没有toString、equals等方法MBG 默认不生成尤其MyBatis3Simple模式需要的话用 LombokData或者手动补updateByExample生成的 SQL 把全表更新了Example 参数为空条件未生效业务层强制校验 Example 条件或直接关掉该方法生成BLOB/TEXT 字段生成到实体类里又大又占内存MBG 默认把longtext映射成 String用columnOverride指定javaType或者单独建视图表抽离 BLOB4.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 的覆盖策略容易让人困惑。overwritetrue并不是说所有文件都会无条件覆盖。它的实际逻辑分两种XML 文件sqlMapGenerator生成的部分生成策略是整体覆盖不管你是否改动过重新生成后 XML 内容跟配置完全一致。Java 文件实体类、Mapper 接口MBG 会解析现有文件的 AST抽象语法树对比要生成的内容。如果现有文件里有 MBG 不会生成的代码比如你手写的方法MBG 会保留你的内容只更新它自己生成的部分。但如果现有文件跟生成内容完全一致就直接覆盖写回。这意味着一个实际场景你在生成的UserMapper.java里加了一个自定义方法findByName重新跑 MBG这个自定义方法会被保留不会被删掉。但如果你在User.java里手动加了一个字段tempFieldMBG 检测到这不是它生成的也会保留。这个机制对增量开发很友好但它有一个副产物——手改过的文件会出现混编状态时间久了文件里既有生成代码又有手写代码代码风格会被打乱。我的建议是生成的 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 typecom.example.MyCommentGenerator property namesuppressAllComments valuetrue/ /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实体类统一加DataMapper 接口不生成任何注解完全靠 Spring 扫包。这样做代码量最省团队统一规则IDE 补全也舒服。4.6 多模块工程下的路径配置不要上来就无脑把 Maven 模块结构复杂化先理解 MBG 的targetProject是相对路径还是绝对路径。targetProject支持相对路径但相对于哪个目录取决于你从哪里执行 Maven 命令。多模块工程里如果你在模块 A 的目录下执行mvn mybatis-generator:generate那targetProjectsrc/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 targetPackagecom.example.model targetProject${generator.model.project}这样无论从哪里执行路径都是以模块的basedir为基准不会出现生成到文件夹外的情况。如果你是多模块工程比如common模块放实体类和 Mapperservice模块放业务代码MBG 生成时需要指定到common模块的 Java 目录和资源目录。我见过很多人配置半天生成到根目录一运行直接报文件已存在都是路径没搞对。4.7 自定义类型处理器当 MBG 的默认映射不够用前面提到类型映射问题这里展开讲一个更进阶的场景当你需要在实体类里存 JSON 字段时。MySQL 5.7 以后支持 JSON 类型但 MBG 的默认映射里JSON 类型没有对应的 Java 类型默认映射成 String然后在 MyBatis 里用一个 TypeHandler 把 JSON 转成对象。操作分三步第一步在表配置里指定columnOverridetable tableNameuser_profile columnOverride columnextra_info javaTypecom.example.model.UserExtraInfo typeHandlercom.example.handler.JsonTypeHandler/ /table第二步写一个 TypeHandlerMappedTypes(UserExtraInfo.class) MappedJdbcTypes(JdbcType.VARCHAR) public class JsonTypeHandler extends BaseTypeHandlerUserExtraInfo { 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(ListUser users) { UserMapper batchMapper sqlSessionTemplate.getMapper(UserMapper.class); for (User user : users) { batchMapper.insert(user); } sqlSessionTemplate.flushStatements(); }但注意SqlSessionTemplate的flushStatements()行为常常让人迷惑而且ExecutorType.BATCH模式下一级缓存里保存的 statement 不会立即执行如果你在同一个事务里接着查询可能查不到刚插入的数据。所以这个方案要处理缓存刷新问题。第二种直接在 Mapper XML 里手写foreach批量 insertinsert idbatchInsert insert into user (id, name, age, created_at) values foreach collectionlist itemitem 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生成Usert_order_items生成OrderItem。别小看命名对齐这件事等你的工程到了几十个实体类的时候命名统一就是最大的效率保障。