MyBatis Generator配置文件全解析:从入门到排查实战
搞Java后端的人只要做过几个正经的数据库项目基本都躲不开MyBatis Generator这个工具。它能把数据库表直接变成实体类、Mapper接口和XML映射文件省掉一大坨纯体力劳动的CRUD代码。但很多人对它的印象就是找个模板改改数据库连接就跑一旦遇到配置文件报错、生成结果不符合预期就完全无从下手。问题基本都出在同一个地方配置文件。MyBatis Generator后面简称MBG的核心逻辑全部由generatorConfig.xml驱动这个XML文件就是整个代码生成流程的总调度中心。它能做什么一句话概括告诉MBG连哪个库、读哪些表、按什么规则生成Java类、生成在哪个包哪个目录、要不要加注释、用不用插件做额外加工。适合谁来读如果你正在用MBG却只是复制粘贴别人配置或者准备从手写Mapper切换到代码生成这篇文章应该能帮你把这些配置项一次吃透。1. 配置文件的整体骨架——先把XML的宪法立起来1.1 DOCTYPE声明与XSD校验为什么编辑器总报错也要写任何一份完整的MBG配置文件第一行都必须有一个DTD声明。很多新手第一次见到这行XML时很困惑为什么带了一堆URL而且IDEA里还会飘红?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这个DOCTYPE的作用是告诉解析器我这个XML的根节点是generatorConfiguration语法规则遵循1.0版本的定义。MBG启动时会尝试从网络拉取这个DTD文件来做校验如果离线环境下拉不到部分版本会直接报错。解法是在本地缓存一份DTD或者把networkConnector关闭——不过实际操作中新版MBG大多已经内置了DTDIDEA的报错多半只是IDE层面的提示不影响命令行执行。配置文件根节点就是generatorConfiguration下面可以挂四个子节点按作用归类节点作用是否必填properties引入外部properties文件可选classPathEntry声明额外的jar包路径可选但有驱动时需要context一个完整的数据源与生成规则上下文必填可配置多个plugin挂载生成插件需放在context内部可选注意plugin和commentGenerator这类加工器是挂在context下面的不是generatorConfiguration的直接子节点。这个层级关系搞错配置文件连校验都过不去。1.2 generatorConfiguration根节点能管什么事generatorConfiguration本身没有太多属性它的核心职责是声明上下文和引入外部资源。在你只配置一个数据库源的情况下一个context就够用了。但MBG支持多context——比如同一个工程里你要从A库生成订单相关的代码从B库生成用户相关的代码就可以写两个context每个context独立指定jdbcConnection、独立指定目标包名和目录。这个特性在拆分中大型项目时非常实用比维护两套配置文件更省心。下面是一个最简配置骨架先整体感受一下generatorConfiguration context idmysqlContext targetRuntimeMyBatis3Simple defaultModelTypeflat jdbcConnection driverClasscom.mysql.cj.jdbc.Driver connectionURLjdbc:mysql://localhost:3306/demo userIdroot password123456 /jdbcConnection javaTypeResolver property nameforceBigDecimals valuefalse/ /javaTypeResolver javaModelGenerator targetPackagecom.example.entity targetProjectsrc/main/java property nametrimStrings valuetrue/ /javaModelGenerator sqlMapGenerator targetPackagemapper targetProjectsrc/main/resources/ javaClientGenerator typeXMLMAPPER targetPackagecom.example.mapper targetProjectsrc/main/java/ table tableNameuser_info/ /context /generatorConfiguration这个骨架里jdbcConnection负责连线javaTypeResolver负责数据库类型到Java类型的换算javaModelGenerator/sqlMapGenerator/javaClientGenerator各管一层输出产物table声明要处理哪些表。下面逐个拆开讲。2. properties与classPathEntry——资源加载的两大细节问题2.1 用properties外部化数据库连接参数刚接触MBG时很多人喜欢把数据库账号密码直接写死在XML里。这个习惯在个人练习项目里无所谓但一旦配置要交给团队使用或者打算提交到Git仓库隐患就来了。MBG的properties节点就是为此设计的它允许你引入一个独立的properties文件然后在XML里用${key}引用其中的值。properties resourcedb.properties/然后在db.properties里写jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/demo jdbc.usernameroot jdbc.password123456XML里对应使用jdbcConnection driverClass${jdbc.driver} connectionURL${jdbc.url} userId${jdbc.username} password${jdbc.password}这里有个小细节resource属性表示从classpath加载而url属性可以表示从文件系统绝对路径加载。用Maven工程时推荐resource方式将db.properties放在src/main/resources下即可非Maven工程如果直接用命令行跑用url指向绝对路径更省事。2.2 classPathEntry的前世今生从驱动jar到Maven依赖classPathEntry这个节点现在看起来有点历史遗留的意思但在某些场景下你还真离不开它。它的作用是给MBG的类加载器添加额外的jar路径。最典型的场景是MBG通过JDBC连接数据库但JDBC驱动jar不在默认classpath里你需要在配置文件中手动声明驱动jar的位置。classPathEntry location/path/to/mysql-connector-java-8.0.33.jar/在Maven工程里通过插件方式运行MBG时比如使用mybatis-generator-maven-plugin插件会自动把项目依赖中的jar引入classpath这时候classPathEntry通常可以省略。但如果你直接用Java命令启动MBG主类这个节点就非常关键——驱动加载不到第一个报错就是ClassNotFound: com.mysql.cj.jdbc.Driver。一个比较隐蔽的坑是jar包版本冲突。假如你的项目用的是MySQL 8.x驱动而classPathEntry指向的是旧版本驱动那连接时会报SSL或加密规则相关错误。所以写classPathEntry时一定要确认驱动的版本和连接串是配套的。经验之谈MySQL 8.x的连接串建议显式加上serverTimezoneAsia/ShanghaiuseSSLfalse不然很多环境会卡在时区或SSL握手的问题上。3. context上下文——整个生成逻辑的心脏3.1 从id到targetRuntime几个容易被忽略的属性context是配置文件里信息量最大的节点它上面可配的属性主要就几个但每一个都直接决定生成代码的长相。id属性只有一个作用——区分多个context方便日志输出时定位。defaultModelType属性控制实体类的生成方式可选值有flat、conditional、hierarchical。这里重点说flat和conditional的区别flat模式下一张表只会生成一个实体类不管表有多少字段全部塞进同一个类conditional模式下如果表有主键MBG会把主键字段单独抽到一个主键类中复杂表还会生成记录类和BLOB类。日常项目里用flat最省心实体类结构简单不容易出现一个POJO拆成三个类导致使用时还得挨个new的情况。targetRuntime属性则决定生成代码的风格。最常用的两个值是MyBatis3和MyBatis3Simple。MyBatis3会生成完整的Example查询体系——也就是每个Mapper都会附带一个XxxExample类支持动态条件查询、排序、去重等MyBatis3Simple则只生成基础的单表CRUD没有Example类。如果你的项目是复杂查询为主、需要灵活拼接SQL条件建议保留MyBatis3如果只是简单增删改查用MyBatis3Simple能让代码量砍掉一大截实体类和XML都会清爽很多。另外还有一个容易忽略的选项beginningDelimiter和endingDelimiter。这两个属性用来指定数据库对象名的包围符。MySQL里默认是反引号如果你要生成order这类SQL保留字当表名的实体就必须设置包围符context idmysqlContext targetRuntimeMyBatis3Simple defaultModelTypeflat beginningDelimiter endingDelimiter3.2 jdbcConnection的细节与数据库方言适配jdbcConnection节点负责建立MBG与数据库的连接它本身还能带上一个connectionFactory子节点用于更精细的连接管理。但日常使用中直接配driverClass、connectionURL、userId、password四个属性就够了。有一点值得注意MBG并不是你想连什么库就能连什么库它的方言支持靠的是context里的targetDatabase属性不过在现在的主流版本里多数情况下MBG能根据连接URL自动识别数据库类型。真正让人踩坑的反而是JDBC连接串的写法。以MySQL为例jdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue这里allowPublicKeyRetrievaltrue在MySQL 8.x的某些连接场景下是必须的否则会报Public Key Retrieval is not allowed。如果连接串里忘了加MBG启动阶段就会挂掉而且报错信息不太直观容易让人误以为是驱动版本问题。连接池类的参数在MBG里不需要关心它只是生成代码不是业务运行时一个连接足够。如果你发现MBG生成的SQL字段顺序和表结构不一致那通常不是连接配置的问题而是元数据读取的缓存或视图问题这个后面再提。3.3 javaTypeResolver类型映射规则的决定者javaTypeResolver决定的是数据库字段类型与Java类型的对应关系。它的核心可配属性其实就一个forceBigDecimals。默认情况下DECIMAL和NUMERIC类型的字段在Java侧可能映射为java.math.BigDecimal也可能映射为Long或Integer取决于字段的精度和小数位数。如果希望一律映射成BigDecimal把forceBigDecimals设为true即可。这在小数金额计算的场景下非常关键——金融项目里精度失之毫厘谬以千里我建议这类项目直接打开。javaTypeResolver property nameforceBigDecimals valuetrue/ /javaTypeResolver除了这个属性javaTypeResolver还有一个隐藏玩法自定义类型解析器。你可以继承org.mybatis.generator.api.JavaTypeResolver接口覆盖calculateJavaType方法自定义数据库类型到Java类型的映射规则。比如有的团队习惯把MySQL的tinyint(1)全部映射成Boolean默认情况下MBG可能会映射为Byte这时候写一个自定义解析器就能统一团队规范。4. 模型、Mapper与SQL映射——三层产物的生成边界4.1 javaModelGenerator实体类的包名、路径与trim策略javaModelGenerator负责生成实体类它有两个必填属性targetPackage目标包名和targetProject目标工程路径指磁盘上的路径。javaModelGenerator targetPackagecom.example.entity targetProjectsrc/main/java property nametrimStrings valuetrue/ property nameconstructorBased valuefalse/ /javaModelGeneratortrimStrings这个属性值得单独讲。它设为true时MBG会在实体类每个String字段的setter方法里自动做value value null ? null : value.trim()把数据库读出的字符串字段首尾空格去掉。这个开关在业务数据不干净的场景下很有用但也要注意如果某些字段本身需要保留空格比如用户输入的地址全局打开trimStrings反而会引入隐性bug。我的习惯是默认关掉只在确有必要时再开避免给运维和数据问题背锅。constructorBased属性控制是否生成带参构造函数。默认false只生成无参构造改为true时会根据所有字段生成一个全参构造方法。对依赖无参构造反序列化的框架比如很多JSON工具或ORM框架来说这个开关务必保持false。还有一个不那么显眼的属性rootClass给实体类指定一个父类。比如团队里所有实体都要继承某个BaseEntity里面统一放id、createTime、updateTime这些公共字段就可以在这里指定。但注意如果表里已经存在与父类同名的字段MBG可能会生成重复字段编译直接报错。这种情况要么改表结构要么在table节点里用ignoreColumn忽略掉父类中已有的字段。4.2 sqlMapGenerator与javaClientGenerator接口与XML的搭配讲究sqlMapGenerator负责生成MyBatis的XML映射文件同样有targetPackage和targetProject两个必填属性。很多人在配targetPackage时会纠结XML文件放到Java包路径里还是resources目录里标准做法是把XML放在resources下包结构跟Mapper接口保持一致。例如接口是com.example.mapper.UserMapperXML路径就是src/main/resources/com/example/mapper/UserMapper.xml。如果targetPackage直接写成com.example.mappertargetProject写成src/main/resources生成的XML就会自动落到正确位置。javaClientGenerator节点负责生成Mapper接口它的type属性决定接口风格。相比XMLMAPPER还有一个ANNOTATEDMAPPER选项——不生成XML直接把SQL注解写在接口方法上。还有MAPPER对应MyBatis3的旧风格。绝大多数项目选XMLMAPPER因为XML里的SQL可读性更好、支持动态SQL也更灵活而且后续你要人工优化SQL时改XML比改注解方便得多。javaClientGenerator同样有targetPackage和targetProject属性。这里有一个非常常见的错误三个人各自的targetProject配的不一样一个写src/main/java一个写src/main/java/一个写绝对路径结果代码生成到不同地方提交代码时冲突一大片。建议整个团队统一用相对于工程根目录的相对路径并且约定死。4.3 table节点一张表的全部控制项table节点是MBG配置里最灵活的部分一张表对应一个table节点。基础写法只需要tableNametable tableNameuser_info/但实际项目里table节点的属性远不止这么简单。domainObjectName属性可以覆盖自动生成的实体类名。比如表名叫user_info默认生成的实体类是UserInfo如果你想叫User就写table tableNameuser_info domainObjectNameUser/mapperName、sqlStatementName等属性可以分别指定Mapper接口名和SQL语句名。不过这些在日常项目里用得不多最常用的还是domainObjectName和下面这几个。enableInsert、enableSelectByPrimaryKey、enableUpdateByPrimaryKey、enableDeleteByPrimaryKey这些开关配合targetRuntime使用可以控制生成哪些基础方法。比如一张字典表你只想生成查询方法不想生成增删改就可以全部关掉table tableNamesys_dict enableInsertfalse enableUpdateByPrimaryKeyfalse enableDeleteByPrimaryKeyfalse/selectByPrimaryKeyQueryId、selectByExampleQueryId这些属性用于生成SQL中的!-- query id --注释常用于配合某些SQL追踪工具做日志定位非必要不配。table节点内部还能嵌套columnOverride、ignoreColumn、generatedKey这几个子节点分别处理字段的覆盖映射、忽略和不生成主键回填。比如表里有个remark字段类型是textMBG默认把它映射成一个长长的String字段生成的实体类比较臃肿你可以在XML里忽略它table tableNameuser_info ignoreColumn columnremark/ /tablegeneratedKey用于配置数据库自增主键的回填。MySQL里典型配置table tableNameuser_info generatedKey columnid sqlStatementMySql identitytrue/ /table这样生成的insert语句会自动带上useGeneratedKeystrue keyPropertyid插入后实体的id会被自动赋值省去一次额外查询。5. 插件体系——从注释压制到日志输出的进阶玩法5.1 官方内置插件逐个看MBG的插件体系非常值得研究它是配置文件的增值层。插件挂在context内部可以理解为一组拦截器——在模型、Mapper、XML生成过程中插入自定义逻辑。官方自带的插件有好几个挑几个最常用的说。org.mybatis.generator.plugins.SerializablePlugin给生成的实体类自动实现java.io.Serializable接口并生成serialVersionUID。如果你们的RPC框架要求DTO必须可序列化这个插件一定要挂上。org.mybatis.generator.plugins.ToStringPlugin为实体类生成toString方法。调试时打印对象能看到字段值比默认的类名哈希值强得多。org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin这个插件解决的是一个隐蔽问题——默认情况下MBG在重新生成XML时会尝试合并已有文件里的自定义内容但合并逻辑偶尔会把XML搞坏。挂上这个插件后每次生成都直接覆盖XML文件不搞智能合并。如果你习惯于生成后手动修改XML且不想被MBG的合并逻辑干扰用它就对了。挂载方式统一写在context内部context idmysqlContext targetRuntimeMyBatis3Simple plugin typeorg.mybatis.generator.plugins.SerializablePlugin/ plugin typeorg.mybatis.generator.plugins.ToStringPlugin/ ... /context5.2 自定义插件的挂载方式自定义插件需要实现org.mybatis.generator.api.Plugin接口或者继承org.mybatis.generator.api.PluginAdapter。举个例子我们团队曾经希望生成的实体类上自动加上ApiModelProperty注解方便Swagger文档直接出参但MBG默认是不生成注解的。于是写了一个插件在modelFieldGenerated方法里根据字段注释生成对应注解。public class SwaggerAnnotationPlugin extends PluginAdapter { Override public boolean modelFieldGenerated(Field field, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) { String remark introspectedColumn.getRemarks(); if (StringUtils.hasText(remark)) { topLevelClass.addImportedType(io.swagger.annotations.ApiModelProperty); field.addAnnotation(ApiModelProperty(\ remark \)); } return true; } }然后在XML里配置plugin typecom.example.plugins.SwaggerAnnotationPlugin/这就是插件的威力整个团队的代码风格约束可以通过MBG自动化落地。同样的思路还能做逻辑删除字段的自动填充、审计字段的自动赋值等关键是找准插件接口的切入时机。6. 踩坑实录——配置文件常见的五类翻车现场6.1 XSD校验失败网络不通时别干等有次在客户的离线环境跑MBG配置文件明明本地写好了一执行就报Failed to read schema document。原因是MBG解析XML时要去拉那个外网DTD但现场网络策略把外网禁了。折腾了半天最终的解法有三种把DTD文件下载好放到本地classpath使用能够跳过DTD校验的解析器或者直接用Maven插件跑——Maven插件对DTD的加载做了本地缓存处理基本不受网络影响。这里给你一个通用办法如果只是IDEA编辑器里飘红不影响MBG运行可以先忽略只有在运行时真的报错才需要处理DTD加载问题。不要一看到网络相关报错就慌着改配置。6.2 表名带前缀下划线导致生成类名异常一张表叫t_sys_user_role_rel默认生成的实体类名会是什么答案是TSysUserRoleRel不是SysUserRoleRel。很多人在这一步就炸了——项目里其他表都按SysUserRoleRel的规范来唯独这张表因为带t_前缀生成了奇怪的名字。原因在于MBG默认的命名规则是移除下划线并转驼峰但保留首字符。解决方案有两个最粗暴的就是在table节点里显式指定domainObjectNametable tableNamet_sys_user_role_rel domainObjectNameSysUserRoleRel/另一个方案是配置一个类名转换器实现org.mybatis.generator.api.IRenameJavaClass接口把表名的公共前缀去掉。如果团队里表命名前缀统一这个方案一劳永逸。6.3 无法加载驱动类jar包路径的两种正确写法MBG运行时找不到驱动类几乎是所有新手都会遇到的问题。具体报错是ClassNotFoundException: com.mysql.jdbc.Driver或者新版驱动的com.mysql.cj.jdbc.Driver。原因很简单JDBC驱动jar不在MBG的classpath里。Maven项目通过插件跑时插件会把依赖的jar自动加到classpath所以很少出这个问题但用独立jar包跑MBG时必须手动指定classPathEntry或把驱动放到MBG的lib目录下。判断思路是这样如果用的是Maven插件先确认pom.xml里有没有引入mysql驱动依赖如果用的是独立jar就必须在XML里写清楚classPathEntry。注意点在于独立运行时classPathEntry的路径既支持相对路径也支持绝对路径但最好使用绝对路径避免当前工作目录不同导致路径失效。6.4 生成代码覆盖冲突同目录重复生成的后果MBG有个特点每次生成都是同一目录下的文件覆盖生成。如果你在targetPackage指向的目录里手工改过代码再跑一遍MBG手工修改会直接丢失。对实体类和Mapper接口来说重新生成覆盖是本分但对XML文件来说MBG默认会尝试合并——它会把已有的XML和新的XML做节点级别的拼接。合并逻辑本身不复杂问题在于它可能保留你删掉的旧SQL也可能把你新增的自定义SQL顶没了。表现就是生成完发现XML里多出一些幽灵节点。我的建议是把MBG生成的文件和手工维护的文件分开存放或者干脆生成到独立的target目录再用构建工具把生成代码拷贝到正式源码目录。这样重复生成不会污染手写代码。6.5 修改表结构后重新生成必须清理缓存有同事改完表结构加了几个字段重新跑MBG结果新字段死活不出现。排查发现是IDE缓存或者数据库连接层面的元数据缓存导致MBG拿到了旧表结构。这里给个标准操作路径确认表结构修改已提交且你连接的确实是对的数据库关闭IDE的数据库工具缓存或刷新数据库连接删除之前生成过的实体类、Mapper接口和XML文件再重新生成。MBG本质上没有增量更新的概念它的覆盖逻辑是删除旧文件中的旧方法追加新方法但如果你改动过表结构最好还是先全量清理再生成避免残留旧的字段映射。另外如果表里新增了非空字段但没有默认值生成出来的insert语句会包含这个字段直接执行就可能报违反非空约束——所以表结构变更后不光要生成代码连现有的数据插入逻辑也要一起review。6.6 一个容易忽略的小坑生成结果乱码最后说一个环境类问题——生成出来的实体类注释里全是乱码。大概率是MBG读取数据库表注释时的编码和工程文件编码不一致。解决办法是确保连接URL里带了characterEncodingutf8同时工程源码文件编码统一UTF-8。如果还是乱码检查一下MySQL表本身的字符集是不是utf8mb4以及操作系统默认字符集是否有干扰。这类问题的共性是不能只改一处得从数据库连接、XML文件编码、IDE编码三个维度同时对齐。写在最后的配置模板速查根据我使用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 properties resourcedb.properties/ context idmysqlContext targetRuntimeMyBatis3Simple defaultModelTypeflat beginningDelimiter endingDelimiter jdbcConnection driverClass${jdbc.driver} connectionURL${jdbc.url} userId${jdbc.username} password${jdbc.password}/ javaTypeResolver property nameforceBigDecimals valuetrue/ /javaTypeResolver javaModelGenerator targetPackagecom.example.entity targetProjectsrc/main/java property nametrimStrings valuefalse/ /javaModelGenerator sqlMapGenerator targetPackagemapper targetProjectsrc/main/resources/ javaClientGenerator typeXMLMAPPER targetPackagecom.example.mapper targetProjectsrc/main/java/ table tableNameuser_info generatedKey columnid sqlStatementMySql identitytrue/ /table table tableNameorder_info generatedKey columnid sqlStatementMySql identitytrue/ /table /context /generatorConfiguration实际动手时我建议你先从一张最简单的表开始跑通整个流程确认产物符合预期后再批量加table节点。别一次性把几十张表全塞进去——万一生成的实体类命名不合预期改起来可比改配置麻烦多了。还有个小习惯每次生成前看一眼当前git状态生成后马上diff一下能第一时间发现是配置改动导致的新差异还是环境干扰导致的脏数据。用MBG求的就是省时间别让配置文件本身成为新的时间黑洞。