
1. 为什么每个MyBatis项目都需要一个代码生成器先聊一个很现实的问题你写一个CRUD接口需要多久我见过太多团队建完表之后实体类手动敲一遍Mapper接口手写一遍XML映射文件再手写一遍单表操作这几个文件加起来少说两三百行。如果是几十张表光这些重复劳动就能耗掉一两天而且枯燥到极点——复制粘贴改字段名最容易出错的地方恰恰就是这种机械劳动。mybatis-generator简称MBG就是来干掉这个重复过程的。它是MyBatis官方提供的代码生成工具连接数据库之后读取表结构信息自动生成对应实体类、Mapper接口、XML映射文件甚至连基本的增删改查方法都帮你写好了。2021年的版本1.4.0及之后在底层做了不少调整比如支持了JSR-310时间类型、生成注释策略也变了网上很多老教程放在今天已经跑不通了这也是我写这篇笔记的原因。这工具适合谁两类人最需要。一类是刚接触MyBatis不久的新手与其对着几百行XML手敲练指法不如让生成器把标准写法打出来你再去研究它为什么长这样学习效率反而更高另一类是维护老项目的开发表结构一变几分钟内重新生成增量代码比手动改几十处要靠谱得多。这篇笔记我会从零开始把配置、运行、结果解读、踩坑排查完整过一遍所有操作都是我本地实测过的尽量做到你照着也能跑通。2. 整体思路与方案选型2.1 为什么选择官方生成器而不是其他插件MyBatis生态里代码生成方案其实不少除了官方MBG还有MyBatis-Plus自带的生成器、MyBatis Generator逆向工程等第三方封装。我做技术选型时更偏向官方工具核心原因有三个。第一个是版本跟随紧密。官方MBG随MyBatis框架同步迭代数据库类型支持、JDBC驱动兼容性都是跟得最紧的。像2021年之后MyBatis 3.5.x普及很多第三方生成器因为维护滞后生成的XML里还是老旧的useGeneratedKeys写法而官方MBG在新版本里已经默认处理得比较合理了。第二个是配置透明可控。MBG的XML配置文件虽然初次看有点繁琐但每一个标签都有明确定义生成什么不生成什么完全由你决定。不像某些封装好的生成器把配置藏在代码里出了问题不好排查。第三个是通用性好。它不绑定任何MyBatis增强框架生成出来的就是标准MyBatis代码无论是纯MyBatis、MyBatis-Plus还是Spring Boot集成都能直接使用或改造。选了它后续切换框架也不会被锁死。2.2 两种主流的运行方式对比MBG有几种运行方式命令行方式、Maven插件方式、Ant Task方式、Java编程方式。对于日常开发我用得最多的是Maven插件方式和Java编程方式命令行方式更多是为了快速验证配置。Maven插件方式的优势是集成度最高在pom.xml里配置好插件后一条mvn mybatis-generator:generate命令就能跑完全流程。它天然适配多模块项目不需要额外管理jar包插件会自动拉取依赖。缺点是如果想在生成前后做定制化处理比如生成后自动调整包路径就需要额外挂exec-maven-plugin。Java编程方式的优势是灵活你可以把生成流程封装成一个工具类在测试类里右键运行或者直接集成到自动化构建脚本里。在配置复杂的场景下比如需要动态传入数据库连接信息、按表名批量生成Java方式明显更有优势。我个人的建议是项目已经用了Maven优先用Maven插件方式因为配置简单团队协作时只要同步pom文件即可如果只是偶尔用一次或者需要频繁调整参数就用Java方式跑一个main方法。后面第三章会分别给出两种方式的完整配置你可以直接抄作业。3. Maven插件方式实操推荐3.1 创建测试环境与依赖准备先用一个最简单的场景练手。假设数据库里有一张用户表t_user字段包括主键id、用户名username、密码password、邮箱email、创建时间create_time。先建好一张测试表数据随便插几条就行。在pom.xml里做两件事引入MyBatis依赖配置MBG插件。插件配置是核心版本号我强烈建议用1.4.0以上版本——这个版本之后生成代码支持了JSR-310日期类型不会再把数据库的datetime类型生成成Date而是自动对应LocalDateTime省去手动转换的麻烦。build plugins plugin groupIdorg.mybatis.generator/groupId artifactIdmybatis-generator-maven-plugin/artifactId version1.4.1/version configuration !-- 配置文件路径 -- configurationFilesrc/main/resources/generatorConfig.xml/configurationFile !-- 允许覆盖生成的XML文件 -- overwritetrue/overwrite !-- 打印生成日志 -- verbosetrue/verbose /configuration dependencies !-- 数据库驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.28/version /dependency /dependencies /plugin /plugins /build这里有两个细节要说明。第一个是overwrite参数。它设置为true时同一张表重复生成XML映射文件会被覆盖但Java类文件不会被覆盖。这是MBG的一个设计策略——XML由机器生成覆盖无风险而Java类你可能会手动改动比如在实体类里额外加了字段或方法所以默认不覆盖避免你的手写代码被冲掉。如果想强制覆盖Java文件需要在配置文件里单独配置overwrite相关参数后面会讲到。第二个是数据库驱动的引入位置。注意插件依赖和项目依赖是两回事必须写在插件的dependencies里否则插件运行时找不到JDBC驱动会直接报ClassNotFoundException。这个坑我见过太多次了驱动明明在项目依赖里有插件就是加载不到。3.2 核心配置文件逐段拆解重点配置文件是整个MBG的核心。直接贴一份完整配置然后逐段解释每个标签的作用。?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 !-- 数据库驱动jar包路径使用Maven插件时可省略 -- classPathEntry location/path/to/mysql-connector-java-8.0.28.jar/ context idmysqlContext targetRuntimeMyBatis3Simple defaultModelTypeflat !-- 注释配置 -- commentGenerator property namesuppressAllComments valuetrue/ /commentGenerator !-- 数据库连接信息 -- jdbcConnection driverClasscom.mysql.cj.jdbc.Driver connectionURLjdbc:mysql://localhost:3306/test_db?useSSLfalseamp;serverTimezoneAsia/Shanghai userIdroot password123456/ !-- 实体类生成配置 -- javaModelGenerator targetPackagecom.example.demo.entity targetProjectsrc/main/java !-- 是否生成实体类的构造方法 -- property nameconstructorBased valuefalse/ !-- 是否使用默认构造方法 -- property nameimmutable valuefalse/ !-- 是否生成toString方法 -- property nametoString valuetrue/ /javaModelGenerator !-- Mapper接口生成配置 -- javaClientGenerator typeXMLMAPPER targetPackagecom.example.demo.mapper targetProjectsrc/main/java/ !-- 表生成配置 -- table tableNamet_user domainObjectNameUser property nameuseActualColumnNames valuefalse/ generatedKey columnid sqlStatementJDBC/ /table /context /generatorConfiguration逐个剖析。classPathEntry指定数据库驱动的jar包位置。用Maven插件方式时这个可以省略因为插件dependencies里已经声明了驱动依赖。如果是命令行方式运行这一项必填否则无法加载驱动。context一个上下文代表一套连接配置和生成规则。注意targetRuntime有两个选项MyBatis3和MyBatis3Simple。后者生成的是精简版代码每个实体类只有基础的增删改查和按主键查询方法代码体积小、可读性强非常适合单表操作为主的项目。前者会额外生成大量的Example类条件构造器包揽了复杂条件查询但生成代码明显膨胀。我建议大部分场景先用MyBatis3Simple真的需要动态条件查询时手动写一个方法加在XML里就够了没必要让官方帮你生成一堆用不上的Example代码。defaultModelTypeflat影响实体类的生成方式。flat模式是每个表对应一个实体类所有字段平铺hierarchical模式则会根据主键、BLOB字段等拆分成多个类结构复杂但便于区分字段类型。默认用flat就够了直观简洁。commentGenerator注释生成策略。默认情况下MBG会根据数据库表的注释信息生成实体类字段注释但中文环境下经常出现乱码问题后面第五章专门讲而且生成的大段英文注释对团队开发没有实际帮助。suppressAllComments设为true后生成代码里不会包含任何注释代码完全由你掌控。有些团队喜欢保留注释那就把它设为false再配合property nameaddRemarkComments valuetrue/来使用数据库表的注释信息但前提是数据库连接参数里要指定useInformationSchematrue否则注释信息仍然读不到。javaModelGenerator实体类生成的位置和规则。targetProject在Maven项目里可以直接写src/main/java如果是多模块项目、生成目录不在当前模块下需要写相对路径或绝对路径。几个property属性的含义constructorBased设为true会生成全参构造方法默认不生成适合需要创建完整对象的场景immutable设为true会去掉setter方法所有字段只能通过构造方法初始化适合值对象模式但日常开发不建议开toString设为true会生成toString方法方便调试打印建议打开。javaClientGeneratorMapper接口生成配置。typeXMLMAPPER表示生成XML映射文件接口的方式这是最标准的MyBatis用法。如果你喜欢注解方式可以改成ANNOTATEDMAPPER但注解方式只适合简单查询复杂SQL还是要写XML所以我始终建议用XMLMAPPER。table表与实体类的映射关系。tableNamet_user明确指定要生成的表domainObjectNameUser指定生成的实体类名不写的话默认根据表名转驼峰命名。useActualColumnNames设为false时数据库字段user_name会自动转成userName设为true则保留数据库原字段名。强烈建议保留false否则Java代码里全是下划线命名违反Java编码规范看着非常别扭。generatedKey主键生成策略配置。columnid告诉生成器哪个字段是自增主键sqlStatementJDBC表示使用JDBC的getGeneratedKeys方法获取自增主键值。这个配置最终会反映在生成的插入语句里自动带上useGeneratedKeystrue keyPropertyid插入后直接能从实体类里拿到自增id非常实用。3.3 运行生成命令与结果验证配置完成后在项目根目录执行命令mvn mybatis-generator:generate如果配置没问题终端会打印出生成日志显示每一张表生成的Java文件和XML文件路径。比如Generating class path entry... mybatis-generator:generate Table t_user generated successfully.生成后看一下目录结构你应该能看到这些文件src/main/java/com/example/demo/ ├── entity/ │ └── User.java └── mapper/ └── UserMapper.java src/main/resources/ └── com/example/demo/mapper/ └── UserMapper.xml打开User.java字段应该对应数据库表结构id是Integer类型createTime是LocalDateTime类型这是1.4.0以上版本的重要变化。UserMapper.java接口里定义了insert、selectByPrimaryKey、selectAll、updateByPrimaryKey、deleteByPrimaryKey五个方法UserMapper.xml里是这五个方法对应的SQL语句。验证方式很简单写一个测试类注入UserMapper调用insert方法插入一条数据再调用selectByPrimaryKey查出来能跑通就说明生成代码完全可用。4. Java代码方式运行与定制化配置4.1 编程方式快速实现Maven插件的配置方式已经能满足大部分需求但如果你想要更灵活的控制比如根据不同的环境动态切换数据库或者按表前缀批量生成我建议用Java编程方式。新建一个测试类写一个main方法调用MBG的ShellRunner或Configuration和MyBatisGenerator。最简单的做法是直接用ShellRunner加载配置文件import org.mybatis.generator.api.ShellRunner; public class GeneratorRunner { public static void main(String[] args) { // 第一个参数是配置文件路径第二个参数是是否覆盖已存在的文件 String[] argsArr {-configfile, src/main/resources/generatorConfig.xml, -overwrite}; ShellRunner.main(argsArr); } }这个方式本质上是把命令行参数搬到Java代码里原理和Maven插件没有区别但优点是调试方便IDEA里右键直接运行不需要每次敲Maven命令。如果你的项目完全没有用Maven只是普通的Java工程这个方式也适用——前提是把MBG的jar包和数据库驱动jar包加到classpath里。更灵活的用法是直接用API方式构建配置完全脱离XML文件import org.mybatis.generator.api.MyBatisGenerator; import org.mybatis.generator.config.*; import org.mybatis.generator.internal.DefaultShellCallback; import java.io.File; import java.util.ArrayList; import java.util.List; public class GeneratorWithCode { public static void main(String[] args) throws Exception { ListString warnings new ArrayList(); boolean overwrite true; File configFile new File(src/main/resources/generatorConfig.xml); Configuration config new ConfigurationParser(warnings).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); } } }这个方式通过ConfigurationParser读取XML解析为配置对象再交给MyBatisGenerator执行。好处是在执行前后可以任意写业务逻辑比如生成完成后自动把实体类复制到另一个模块、或者在生成前动态修改配置对象。4.2 多表批量生成的配置技巧实际项目中很少只生成一张表更多场景是生成整个数据库或指定前缀的多张表。在table标签里做点手脚就能实现。tableName支持SQL通配符%比如tableNamet_%会匹配所有以t_开头的表。但要注意如果tableName用了通配符domainObjectName就不能写死否则多张表会生成同一个类名直接报错。这时不写domainObjectNameMBG会自动根据表名转换类名比如t_user生成Usert_order生成Order。还有一种需求是只需要某几张表。可以配置多个table标签或者用table tableNameuser|order|product这样的竖线分隔写法仅当enableInsert之类的属性不需要个性化配置时可行。我实际用得最多的一个配置组合是table tableNamet_% property nameuseActualColumnNames valuefalse/ generatedKey columnid sqlStatementJDBC/ /table一条配置生成所有业务表所有表统一使用id作为自增主键——这是比较标准的表设计约定。如果你的表设计里有联合主键或者不使用自增主键需要针对这些特殊表单独再加一个table标签覆盖默认配置。4.3 配置文件里的几个容易忽略的参数有很多参数不太起眼但实际影响很大我整理几个高频使用的。javaTypeResolver类型解析器。默认会把数据库的decimal类型映射为BigDecimal这是安全的选择。如果业务上明确知道某个字段不会出现小数可以配置forceBigDecimals为false让decimal映射为Double或Float减少类型转换的麻烦。不过稳妥起见涉及金额的字段还是建议保持BigDecimal。table标签内的enableInsert、enableUpdateByPrimaryKey、enableDeleteByPrimaryKey、enableSelectByPrimaryKey等属性分别控制是否生成对应的方法。如果你只需要查询功能可以把enableInsert设为false生成的Mapper方法列表会干净很多。sqlMapGenerator指定XML文件的生成目录。这个标签经常被人漏掉。如果javaClientGenerator配了XMLMAPPER但没配sqlMapGeneratorMBG会报错——它不知道XML该放哪。配置方式如下sqlMapGenerator targetPackagemapper targetProjectsrc/main/resources /sqlMapGenerator注意targetPackage的写法。如果这里写com.example.mapperXML文件会生成在src/main/resources/com/example/mapper/目录。但实际项目里XML通常直接放在resources/mapper/下所以targetPackage直接写mapper就行。5. 生成结果解读与常见问题排查5.1 生成代码的结构分析与改造建议拿到生成的代码之后不要急着直接往项目里塞先花几分钟理解每一部分。User.java实体类非常简单就是数据库字段的Java映射没有任何业务逻辑。需要注意的是MBG生成的实体类没有实现序列化接口如果想缓存实体到Redis或做分布式传输需要自己加上implements Serializable。这也是我每次生成完必做的第一步改造。UserMapper.java接口是空壳子只有方法声明。MBG的核心逻辑全部在XML里。UserMapper.xml是重点。打开看一下resultMap定义了字段映射关系Base_Column_List定义了查询时默认查询的字段列表五个SQL语句分别是增删改查。你可以看到insert语句里用了useGeneratedKeys和keyProperty这正是前面generatedKey配置生效的结果。这里说一个很多人都会忽略的问题生成的updateByPrimaryKey是一个全字段更新操作——哪个字段不为空就更哪个字段。这个行为适合明确知道要更新哪些字段的场景但也意味着如果你只更新一个字段其他字段会被置为NULL。实际开发中我通常会根据业务需求手写一个updateByPrimaryKeySelective方法只更新非空字段。生成的代码是起点不是终点按需改造是正常流程。5.2 注释乱码问题——高概率踩坑生成代码里的中文注释出现乱码是我见过最多的问题。现象是生成的实体类里字段注释显示成一堆???或乱码。原因有两层。第一层是数据库连接URL里没有指定字符编码。MySQL 8默认连接字符集是utf8mb4如果连接URL里没显式指定中文注释经过JDBC传输可能丢失。解决办法是在connectionURL里加上jdbc:mysql://localhost:3306/test_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8注意XML文件里符号要转义为amp;所以实际配置里是这样的jdbcConnection driverClasscom.mysql.cj.jdbc.Driver connectionURLjdbc:mysql://localhost:3306/test_db?useSSLfalseamp;serverTimezoneAsia/Shanghaiamp;characterEncodingutf8 userIdroot password123456/第二层是MBG读取表注释信息需要显式开启。在jdbcConnection下加一个属性property nameuseInformationSchema valuetrue/这样MBG才会通过JDBC的DatabaseMetaData接口获取表和字段的注释信息否则它默认用可选的Remark信息经常拿不到中文注释。两个配置都加上之后中文注释基本就能正常生成了。5.3 Example类的取舍策略如果你用的是MyBatis3而不是MyBatis3Simple生成的代码里会多出一个UserExample.java。这个类是为复杂查询设计的支持where条件动态拼接、排序、分页等功能。我的态度是能用Simple模式就不用Example。原因有几个。一是Example代码生成后非常臃肿一个简单的表可能生成一千多行代码维护起来心里发慌二是Example的使用方式比较绕要创建Criteria、拼条件代码可读性差新接手的人需要额外学习三是真正复杂的动态查询场景手写XML反而更清晰可控。如果项目遗留代码已经用了Example模式也不需要急着重构。Example类本质上就是在Java里构造SQL的where条件熟练之后也不算难用。它最适用的场景是管理后台的列表筛选——条件多、字段动态、组合复杂用Example能省不少事。5.4 生成代码与Lombok的整合方案现在很多项目用Lombok简化实体类的getter/setter但MBG默认生成的是完整的getter和setter方法。整合方式有两种。第一种是生成后再手动删除getter/setter在实体类上加上Data注解。每次重新生成都要重复这个操作比较繁琐。第二种更优雅通过配置让MBG直接不生成getter/setter方法。在javaModelGenerator里加两个属性javaModelGenerator targetPackagecom.example.demo.entity targetProjectsrc/main/java !-- 不生成getter方法 -- property namegenerateGetter valuefalse/ !-- 不生成setter方法 -- property namegenerateSetter valuefalse/ /javaModelGenerator然后自己在实体类上手动加Data注解。这样生成结果非常干净实体类只保留字段定义和注解配合Lombok使用。5.5 常见问题排查速查表把实际使用中频率最高的问题整理成一张表方便参考。问题现象可能原因解决方式运行时报ClassNotFoundException: com.mysql.cj.jdbc.Driver插件没有引入数据库驱动在Maven插件的dependencies里加入mysql-connector-java生成的实体类没有注释未开启注释读取在jdbcConnection里加property nameuseInformationSchema valuetrue/注释显示乱码连接URL未指定字符集在connectionURL里加characterEncodingutf8XML文件总是被覆盖默认配置导致XML文件由MBG管理覆盖属正常行为Java文件不覆盖生成Java文件不覆盖默认安全策略这是设计行为如需覆盖需额外配置LocalDateTime生成成了Date使用旧版本MBG升级到1.4.0以上版本生成XML文件时找不到目录未配置sqlMapGenerator添加该标签并指定targetPackage和targetProject重复生成时接口里的手写方法丢失Java文件不可覆盖手写方法建议写到独立的类或写在XML中公用生成的方法太少没有条件查询使用了MyBatis3Simple切换targetRuntime为MyBatis3生成Example类插入后无法获取自增主键未配置generatedKey在table里加generatedKey columnid sqlStatementJDBC/这些都是我实际遇到过并且解决过的问题照着排查基本能覆盖95%的异常场景。6. 从生成到落地项目集成的几个要点6.1 生成代码在Spring Boot中的衔接生成代码如何接入Spring Boot项目是很多人最后卡住的一步。MBG只负责生成文件不负责管理Bean生命周期所以要让Maper接口能被Spring管理还需要做两件事。第一件是在启动类上扫描Mapper接口。在Spring Boot启动类上加MapperScan(com.example.demo.mapper) SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }MapperScan会扫描指定包下的所有Mapper接口为它们生成代理实现类并注册为Spring Bean。如果没有这个注解运行时会报No qualifying bean of type UserMapper错误。第二件是确保XML文件能被MyBatis加载。在application.yml里配置mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.demo.entitymapper-locations指定XML映射文件的路径type-aliases-package指定实体类所在包这样XML里写resultTypeUser时可以省略全限定类名代码更清爽。这两个配置配好之后在Service层注入Mapper接口直接调用方法即可。6.2 增量生成的策略设计项目维护阶段表结构经常变动——加一个字段、改一个字段类型这时候需要重新生成代码。MBG支持增量生成但需要设计好策略。我的做法是实体类允许覆盖字段变了实体类里的属性必须同步Mapper接口手写的方法保留不覆盖XML文件允许覆盖但手写的自定义SQL会丢。所以手写的自定义SQL尽量不要放在MBG生成的XML里要么单独建一个XML文件要么在Mapper接口里用注解。具体操作上重新生成前把Mapper接口里手写的方法先复制出来生成完再贴回去。为了避免这个麻烦我一般把手写的扩展方法放在单独的接口里比如UserMapperExt.java继承MBG生成的基础接口public interface UserMapperExt extends UserMapper { // 手写扩展方法 ListUser selectByAgeRange(Integer minAge, Integer maxAge); }这样即使每次重新生成UserMapper扩展接口都不会受影响。这是一个很实用的架构思路建议一早就规划好。6.3 多数据源场景的注意事项多数据源项目使用MBG时有几个特殊点。一个context只能配置一个数据源所以多数据源时要配置多个context每个context里分别指定不同的jdbcConnection和targetPackage。不过如果两张表在不同数据库但实体类想放同一个包只要两个context的targetPackage一致就行。还有一点不同数据库驱动类不一样。比如Oracle用的是oracle.jdbc.driver.OracleDriver连接URL格式也和MySQL不同。多数据源场景下每个context的jdbcConnection里的driverClass、connectionURL、userId、password都要单独配置不能共用。7. 一些写在最后的经验MBG用熟了之后我最大的感受是它帮你省掉的不是写代码的时间而是切换上下文的精神损耗。写CRUD本来不需要动脑但又不得不写这种状态最消耗耐心。让工具干这种事人才有精力去思考真正复杂的业务。最后一个建议拿到生成代码之后花点时间通读一遍XML文件。MBG生成的SQL写法非常标准尤其是ResultMap的字段映射、主键回填、批量操作的写法多看几遍你手写SQL的质量也会提高。很多团队允许新人用MBG生成入门代码本质上是让新人先学会“正确的写法长什么样”再逐步脱离工具手写。这也是我觉得这个工具对新手最有价值的地方。根据我个人经验第一次配置MBG时不要追求一步到位先用最简单的单表配置跑通流程再逐步添加表、调整参数。环境越简单排错越容易等你理解了每个参数的意义再上复杂配置就完全不慌了。