☰
MyBatis Generator配置文件全解析:从入门到排查实战
2026/10/15 14:54:34 网站建设 项目流程

搞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 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">

这个DOCTYPE的作用是告诉解析器:我这个XML的根节点是generatorConfiguration,语法规则遵循1.0版本的定义。MBG启动时,会尝试从网络拉取这个DTD文件来做校验,如果离线环境下拉不到,部分版本会直接报错。解法是在本地缓存一份DTD,或者把networkConnector关闭——不过实际操作中,新版MBG大多已经内置了DTD,IDEA的报错多半只是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 id="mysqlContext" targetRuntime="MyBatis3Simple" defaultModelType="flat"> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/demo" userId="root" password="123456"> </jdbcConnection> <javaTypeResolver> <property name="forceBigDecimals" value="false"/> </javaTypeResolver> <javaModelGenerator targetPackage="com.example.entity" targetProject="src/main/java"> <property name="trimStrings" value="true"/> </javaModelGenerator> <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"/> <javaClientGenerator type="XMLMAPPER" targetPackage="com.example.mapper" targetProject="src/main/java"/> <table tableName="user_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 resource="db.properties"/>

然后在db.properties里写:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/demo jdbc.username=root jdbc.password=123456

XML里对应使用:

<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的连接串建议显式加上serverTimezone=Asia/Shanghai&useSSL=false,不然很多环境会卡在时区或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 id="mysqlContext" targetRuntime="MyBatis3Simple" defaultModelType="flat" beginningDelimiter="`" endingDelimiter="`">

3.2 jdbcConnection的细节与数据库方言适配

jdbcConnection节点负责建立MBG与数据库的连接,它本身还能带上一个connectionFactory子节点用于更精细的连接管理。但日常使用中,直接配driverClass、connectionURL、userId、password四个属性就够了。

有一点值得注意:MBG并不是你想连什么库就能连什么库,它的方言支持靠的是context里的targetDatabase属性,不过在现在的主流版本里,多数情况下MBG能根据连接URL自动识别数据库类型。真正让人踩坑的反而是JDBC连接串的写法。以MySQL为例:

jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true

这里allowPublicKeyRetrieval=true在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 name="forceBigDecimals" value="true"/> </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 targetPackage="com.example.entity" targetProject="src/main/java"> <property name="trimStrings" value="true"/> <property name="constructorBased" value="false"/> </javaModelGenerator>

trimStrings这个属性值得单独讲。它设为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.UserMapper,XML路径就是src/main/resources/com/example/mapper/UserMapper.xml。如果targetPackage直接写成com.example.mapper,targetProject写成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节点。基础写法只需要tableName:

<table tableName="user_info"/>

但实际项目里,table节点的属性远不止这么简单。

domainObjectName属性可以覆盖自动生成的实体类名。比如表名叫user_info,默认生成的实体类是UserInfo,如果你想叫User,就写:

<table tableName="user_info" domainObjectName="User"/>

mapperName、sqlStatementName等属性可以分别指定Mapper接口名和SQL语句名。不过这些在日常项目里用得不多,最常用的还是domainObjectName和下面这几个。

enableInsert、enableSelectByPrimaryKey、enableUpdateByPrimaryKey、enableDeleteByPrimaryKey这些开关配合targetRuntime使用,可以控制生成哪些基础方法。比如一张字典表你只想生成查询方法,不想生成增删改,就可以全部关掉:

<table tableName="sys_dict" enableInsert="false" enableUpdateByPrimaryKey="false" enableDeleteByPrimaryKey="false"/>

selectByPrimaryKeyQueryId、selectByExampleQueryId这些属性用于生成SQL中的<!-- query id -->注释,常用于配合某些SQL追踪工具做日志定位,非必要不配。

table节点内部还能嵌套columnOverride、ignoreColumn、generatedKey这几个子节点,分别处理字段的覆盖映射、忽略和不生成主键回填。比如表里有个remark字段,类型是text,MBG默认把它映射成一个长长的String字段,生成的实体类比较臃肿,你可以在XML里忽略它:

<table tableName="user_info"> <ignoreColumn column="remark"/> </table>

generatedKey用于配置数据库自增主键的回填。MySQL里典型配置:

<table tableName="user_info"> <generatedKey column="id" sqlStatement="MySql" identity="true"/> </table>

这样生成的insert语句会自动带上useGeneratedKeys="true" keyProperty="id",插入后实体的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 id="mysqlContext" targetRuntime="MyBatis3Simple"> <plugin type="org.mybatis.generator.plugins.SerializablePlugin"/> <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/> ... </context>

5.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 type="com.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节点里显式指定domainObjectName:

<table tableName="t_sys_user_role_rel" domainObjectName="SysUserRoleRel"/>

另一个方案是配置一个类名转换器,实现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拿到了旧表结构。这里给个标准操作路径:

  1. 确认表结构修改已提交,且你连接的确实是对的数据库;
  2. 关闭IDE的数据库工具缓存(或刷新数据库连接);
  3. 删除之前生成过的实体类、Mapper接口和XML文件,再重新生成。

MBG本质上没有"增量更新"的概念,它的覆盖逻辑是"删除旧文件中的旧方法,追加新方法",但如果你改动过表结构,最好还是先全量清理再生成,避免残留旧的字段映射。另外,如果表里新增了非空字段但没有默认值,生成出来的insert语句会包含这个字段,直接执行就可能报违反非空约束——所以表结构变更后,不光要生成代码,连现有的数据插入逻辑也要一起review。

6.6 一个容易忽略的小坑:生成结果乱码

最后说一个环境类问题——生成出来的实体类注释里全是乱码。大概率是MBG读取数据库表注释时的编码和工程文件编码不一致。解决办法是确保连接URL里带了characterEncoding=utf8,同时工程源码文件编码统一UTF-8。如果还是乱码,检查一下MySQL表本身的字符集是不是utf8mb4,以及操作系统默认字符集是否有干扰。这类问题的共性是不能只改一处,得从数据库连接、XML文件编码、IDE编码三个维度同时对齐。

写在最后的配置模板速查

根据我使用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> <properties resource="db.properties"/> <context id="mysqlContext" targetRuntime="MyBatis3Simple" defaultModelType="flat" beginningDelimiter="`" endingDelimiter="`"> <jdbcConnection driverClass="${jdbc.driver}" connectionURL="${jdbc.url}" userId="${jdbc.username}" password="${jdbc.password}"/> <javaTypeResolver> <property name="forceBigDecimals" value="true"/> </javaTypeResolver> <javaModelGenerator targetPackage="com.example.entity" targetProject="src/main/java"> <property name="trimStrings" value="false"/> </javaModelGenerator> <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"/> <javaClientGenerator type="XMLMAPPER" targetPackage="com.example.mapper" targetProject="src/main/java"/> <table tableName="user_info"> <generatedKey column="id" sqlStatement="MySql" identity="true"/> </table> <table tableName="order_info"> <generatedKey column="id" sqlStatement="MySql" identity="true"/> </table> </context> </generatorConfiguration>

实际动手时,我建议你先从一张最简单的表开始跑通整个流程,确认产物符合预期后,再批量加table节点。别一次性把几十张表全塞进去——万一生成的实体类命名不合预期,改起来可比改配置麻烦多了。还有个小习惯:每次生成前看一眼当前git状态,生成后马上diff一下,能第一时间发现是配置改动导致的新差异,还是环境干扰导致的脏数据。用MBG求的就是省时间,别让配置文件本身成为新的时间黑洞。

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

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

立即咨询