☰
MyBatis逆向工程全流程实战:从建表到生成Spring Boot数据访问层代码
2026/10/1 4:12:18 网站建设 项目流程

做Java后端的,谁没被“建表一时爽,写完增删改查已下班”坑过?一张业务表落地之后,实体类、Mapper接口、XML映射文件、Example查询条件,全是一模一样的重复劳动。MyBatis逆向工程解决的就是这个问题——把数据库表结构当成唯一数据源,自动产出整套数据访问层代码,让开发者把精力留给真正的业务逻辑。我从MyBatis 3.2时代就开始用官方Generator跑生成,后来在Spring Boot项目里反复调整配置,踩过不少坑,也总结出一套稳定玩法。这篇文章不打算堆概念,就把一套“清晰简洁”的逆向工程方案完整拆开:从工具选型、配置逐行解读、完整实操,到常见翻车现场和进阶改造,一步步说清楚,适合刚接触逆向工程的新手,也适合想优化现有生成流程的老手。

1. 用之前先想清楚:逆向工程到底解决什么问题

很多人在项目里引入MyBatis Generator(后文简称MBG),理由是“别人都在用”,结果生成完代码反而嫌它啰嗦,最后又删掉手写。根本原因不是工具不好,而是没想清楚它解决的是哪一层问题。

1.1 它不只是一堆模板,更是一套“表到Java”的映射约束器

MBG的核心思路很简单:数据库表字段是唯一事实来源,按配置好的规则,一次性生成实体类、Mapper接口、XML映射文件、Example类。这个思路的价值不只是替你打字,而是把“列名”和“Java属性”之间的对应关系固定下来。

手写实体类时最常见的毛病就是漏字段、类型写错、命名不统一。比如MySQL里user_name这一列,有人写成userName,有人写成username,有人写UserName,代码风格全看个人心情。而MBG默认开启驼峰转换,user_name稳定映射为userName,order_sn稳定映射为orderSn。我用它的真实感受是:它输出的不只是代码,而是一个“列名到字段名”的规范层,项目里所有人拿到同一个表结构,生成的实体类完全一致。

1.2 一张表跑通的效率差距

简单算笔账。假设订单表有40个字段,手写实体类加上getter/setter至少50行,Mapper接口十几个方法约30行,XML里BaseResultMap、Base_Column_List、insert、updateByPrimaryKeySelective等基础SQL两个文件加起来300行上下。一张表平均工作量300到400行模板代码,10张表就是3000行以上,而且全部是手速活,没有任何技术含量。

MBG跑一遍,MySQL建好表后,一条命令或者一个插件的点击,10张表代码一分钟内全部落地,生成的Mapper、XML还自带规范注释。这个效率对比,在小项目上差距只是“午休半小时写三张表”,在中大型项目上差距直接是“少招一个只会复制粘贴的实习生”。所以我的判断标准很简单:只要项目用MyBatis且表结构是新的,就默认跑MBG当地基,没人会跟自己的时间过不去。

1.3 什么时候别用它

不过工具不是万能的,我见过最糟糕的用法是把MBG用在完全不该用的地方。三种场景我强烈不建议引入:

第一种,核心业务模块里已有大量手写SQL的老Mapper。MBG重新生成会覆盖同名Java文件,如果你把业务方法直接写在原Mapper接口里,一跑生成就是灾难。这种存量代码动它没意义,维持原样更稳。

第二种,表结构极度不规范的历史遗留项目。比如一个字段同时承担多种状态含义、列名全拼音缩写、同一张表里既有下划线又有单字母前缀,生成出来的代码要手工清洗的工程量比手写还大,工具帮不上忙。

第三种,团队里已经没有统一规范的维护者。MBG生成的代码风格统一,但如果没人持续维护表结构变更和对应的重新生成流程,代码会渐渐腐化到和手写没区别,反而多了一层“看起来规范”的假象。

工具不是银弹。它的定位是“轻松完成80%的基础CRUD”,剩下真正复杂的20%,还是要用手写SQL配合领域逻辑去补齐。

2. 选型与准备:三种接入方式怎么挑

确定了要用MBG,下一步是选接入方式。这里先说结论:常规项目直接用Maven插件方式,简单可控,配置随项目走;一次性、临时性的代码生成,用IDEA插件;需要在程序里动态触发、比如根据测试库自动生成代码的,才用Java编码方式。

2.1 三种方式对比

我早年习惯用IDEA插件点一下生成,后来发现在多人团队里这种方式有个致命问题:插件版本不统一,A同学电脑生成的和B同学电脑生成的结果可能不一样,版本一升级某段SQL输出风格就变了,代码差异Review起来很心烦。

后来我把所有项目切到Maven插件方式,配置写进pom.xml,终极执行命令是mvn mybatis-generator:generate。好处很直接:插件版本锁定在pom里,任何成员拉下来跑同一条命令,生成结果一致;生成的配置、数据库连接串也全部入库,换环境只改properties文件,不会出现“我电脑上能跑你电脑上跑不了”。

写代码方式适合极少数场景,比如写一个启动任务,每次发布前自动从测试库生成代码到指定目录,配合CI/CD流水线用。常规开发不推荐,因为配置逻辑写在Java代码里,执行入口不显眼,别人接手要翻代码才知道生成规则。

接入方式上手难度配置可追溯适合场景不建议场景
Maven插件低高,配置入库常规项目、团队协作一次性临时生成
IDEA插件最低低,依赖个人环境本地快速尝试多人协作统一规范
Java编码高中,要读代码理解自动化流程、生成任务日常开发使用

2.2 pom依赖与插件配置

Maven方式需要加两个东西:核心库和插件。核心库mybatis-generator-core提供生成引擎,插件mybatis-generator-maven-plugin负责把命令行和引擎串起来。实际操作中核心库版本不一定非写不可,插件依赖会自动带,但显式写出来更明确。

最小可用配置长这样:

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

代码里有个常用参数overwrite,我建议设成true,后面讲坑位时会细说覆盖规则。插件的configurationFile指定外部配置文件路径,而不是把全部配置堆到pom里,这样XML可以独立维护,也方便在多个模块间复用。

2.3 数据库连接串与驱动,先绕开最大的坑

无论选哪种接入方式,generatorConfig.xml里都要配数据库连接。MySQL 8用户这里最容易踩坑。老教程会教你写com.mysql.jdbc.Driver,但MySQL 8之后必须用com.mysql.cj.jdbc.Driver,URL也最好加上时区和SSL参数:

jdbc:mysql://localhost:3306/mall?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8

useSSL=false是防止MySQL 8默认SSL握手报警告,serverTimezone=Asia/Shanghai是避免程序时区和数据库不一致导致时间字段生成异常,characterEncoding=utf8保证中文注释不乱码。这三个参数的坑我下面在“翻车现场”还会展开,这里先记住:复制别人的连接串时,老版本配置不能直接套到MySQL 8上。

3. 核心配置逐行拆解:generatorConfig.xml详解

整个逆向工程的重头戏是generatorConfig.xml。我见过有人从网上复制一份配置直接用,生成结果和自己预期差了十万八千里,根本原因是对每个节点的作用没搞清楚。

3.1 配置骨架:每个节点存在的意义

一个标准的配置文件结构是这样的:

<generatorConfiguration> <properties resource="db.properties"/> <context id="MysqlContext" targetRuntime="MyBatis3Simple" defaultModelType="flat"> <jdbcConnection connectionURL="${jdbc.url}" userId="${jdbc.username}" password="${jdbc.password}"/> <javaTypeResolver> <property name="forceBigDecimals" value="true"/> <property name="useJSR310Types" value="true"/> </javaTypeResolver> <javaModelGenerator targetPackage="com.example.entity" targetProject="src/main/java"/> <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"/> <javaClientGenerator type="XMLMAPPER" targetPackage="com.example.mapper" targetProject="src/main/java"/> <table tableName="t_order" domainObjectName="Order"/> </context> </generatorConfiguration>

我逐层讲一下。properties标签可以把数据库连接信息抽到外部db.properties文件里,这样不同环境只改一个文件,不用动生成配置。context的id是生成上下文标识,可以存在多个,但一般一个就够;targetRuntime="MyBatis3Simple"表示生成简化版Mapper,不带Example类,这个选择很重要,后面单独说。

javaModelGenerator控制实体类输出位置和包名,sqlMapGenerator控制XML映射文件输出位置,javaClientGenerator的type="XMLMAPPER"代表生成的是Mapper接口加XML的标准组合,如果选ANNOTATEDMAPPER就会把SQL直接写在注解里,不生成XML文件。这三个生成器的targetPackage和targetProject决定代码落在哪,配置错一步,生成结果就跑到不想去的地方。

3.2 table节点:哪些表生成,全在这里控制

table节点是真正的核心,一个table对应一张数据库表,也可以批量配置。实际项目中我不建议把所有表都自动匹配,因为不同表的需求差异很大。

tableName="t_order"指定表名,domainObjectName="Order"指定生成实体类的类名,如果不写,MBG会按表名转驼峰,t_order会生成TOrder,别问我怎么知道的,丑到没法看。表名里的前缀最好手动指定DomainObjectName处理掉。

生成哪些方法,是table节点里最该花心思的地方。比如一张配置表只需要查询,就可以关掉所有增删改:

<table tableName="t_config" domainObjectName="Config"> <generatedKey column="id" sqlStatement="JDBC"/> <columnOverride column="status" javaType="java.lang.Integer" jdbcType="TINYINT"/> <ignoreColumn column="remark"/> </table>

generatedKey告诉MBG主键是数据库自增的,生成insert后自动把生成的主键回填到实体的id属性,这个在业务里非常常用。columnOverride是把某一列强制映射成指定Java类型。ignoreColumn是直接忽略某列,不生成到实体类里。

3.3 三个容易被忽视却影响生成质量的隐藏项

第一个是useJSR310Types。不开启时,数据库的datetime字段默认生成java.util.Date,这在现代项目中已经不受欢迎。开启后,datetime映射为LocalDateTime、date映射为LocalDate,代码清爽很多。具体配置在javaTypeResolver下面加一行<property name="useJSR310Types" value="true"/>。

第二个是forceBigDecimals。数据库decimal(10,2)在不配置时可能被生成成java.math.BigDecimal,但当列类型是float或double时,默认映射成Java的基本类型,金额字段容易丢精度。想统一处理时,开启forceBigDecimals,让所有浮点列统一为BigDecimal,尤其做支付类项目必须开。

第三个是targetRuntime的选择。MyBatis3会生成完整的Example方法体系,MyBatis3Simple则只生成单表基础CRUD,不生成Example类。我在第5章会专门讲Example类要不要留的判断标准。保守建议:业务复杂的模块用MyBatis3保留Example,对外简单数据源用MyBatis3Simple,代码干净得多。

4. 完整实操:从一个订单表跑到Spring Boot里

光讲配置不跑一遍都是纸上谈兵。这里从一个带“坑”的订单表出发,跑完整流程,看看生成的代码长什么样,怎么接进Spring Boot。

4.1 建一个故意带坑的表

为了把类型映射、命名转换、自增主键这些关键点都演示出来,我建了这么一张表:

CREATE TABLE `t_order` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `order_sn` varchar(64) NOT NULL COMMENT '订单编号', `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID', `order_amount` decimal(10,2) DEFAULT NULL COMMENT '订单金额', `pay_status` tinyint(4) DEFAULT '0' COMMENT '支付状态:0未支付 1已支付', `is_deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除标记', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

表设计里包含了下划线列名、decimal金额、tinyint状态、tinyint(1)布尔标记、datetime时间字段。生成之后能一眼看出好多映射规则顺不顺手。

4.2 完整的generatorConfig.xml直接复刻

项目结构按Maven标准来,数据库连接抽到db.properties:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mall?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8 jdbc.username=root jdbc.password=123456

generatorConfig.xml放在src/main/resources下:

<generatorConfiguration> <properties resource="db.properties"/> <context id="OrderContext" targetRuntime="MyBatis3Simple" defaultModelType="flat"> <property name="beginningDelimiter" value="`"/> <property name="endingDelimiter" value="`"/> <jdbcConnection driverClass="${jdbc.driver}" connectionURL="${jdbc.url}" userId="${jdbc.username}" password="${jdbc.password}"/> <javaTypeResolver> <property name="useJSR310Types" value="true"/> <property name="forceBigDecimals" value="true"/> </javaTypeResolver> <javaModelGenerator targetPackage="com.example.entity" 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="t_order" domainObjectName="Order"> <generatedKey column="id" sqlStatement="JDBC"/> </table> </context> </generatorConfiguration>

两个细节值得说明。beginningDelimiter和endingDelimiter配成反引号,是因为MySQL保留字问题,比如列名碰巧叫status、order,生成SQL时会自动加反引号,避免运行时报语法错误。trimStrings会让生成的实体类对String类型字段做trim处理,去掉数据库返回字符串首尾空格,这个属性不是默认开启的,对做前后端交互的系统很有用。

然后执行生成命令:

mvn mybatis-generator:generate

执行完,src/main/java下生成com.example.entity.Order和com.example.mapper.OrderMapper,src/main/resources/mapper下生成OrderMapper.xml。

4.3 生成结果里哪些能直接用,哪些必须改造

运行完成后打开Order.java,会发现这些映射结果:

  • order_sn生成为orderSn,驼峰转换符合预期。
  • order_amount因为开了forceBigDecimals,类型是BigDecimal,金额精算不会出问题。
  • pay_status的tinyint(4)映射成Integer,is_deleted的tinyint(1)映射成Boolean。
  • create_time和update_time因为开了useJSR310Types,类型是LocalDateTime。

OrderMapper.java由于用的是MyBatis3Simple,只有五个方法:deleteByPrimaryKey、insert、selectByPrimaryKey、updateByPrimaryKey、updateByPrimaryKeySelective,没生成一堆Example方法,简洁够用。

updateByPrimaryKeySelective是日常业务最爱用的方法,它只更新传入的非null字段。比如只改订单状态,就构造一个Order只塞id和payStatus,调用updateByPrimaryKeySelective,生成SQL只会更新pay_status这一列,其他字段不会被误写。相比之下updateByPrimaryKey会把所有字段更新一遍,容易把不相干的字段覆盖成null,我强烈建议业务代码里少用。

直接能用的部分约占整体代码的90%。真正需要改造的是三块业务逻辑:逻辑删除字段is_deleted不会被自动处理,生成代码里它只是一个普通字段,需要自己在业务方法里更新;createTime和updateTime虽然是LocalDateTime,但插入时需要手动set当前时间,没有自动填充机制;乐观锁字段如果有version列,同样得在SQL里手动加where version = ?。

4.4 接进Spring Boot使用

生成代码接入Spring Boot很简单。启动类或配置类上加@MapperScan("com.example.mapper"),或者在Mapper接口上直接加@Mapper注解,然后Service里注入使用:

@Service public class OrderService { private final OrderMapper orderMapper; public OrderService(OrderMapper orderMapper) { this.orderMapper = orderMapper; } public Order getOrderById(Long id) { return orderMapper.selectByPrimaryKey(id); } public Long createOrder(Order order) { order.setCreateTime(LocalDateTime.now()); order.setUpdateTime(LocalDateTime.now()); orderMapper.insert(order); return order.getId(); } public void markPaid(Long orderId) { Order update = new Order(); update.setId(orderId); update.setPayStatus(1); orderMapper.updateByPrimaryKeySelective(update); } }

看到没有,insert执行后order.getId()能拿到自增主键,这就是配置里generatedKey的作用。到这里,一张表从建库到跑通Service层接口,全程不超过两分钟,干净利落。

5. 坑位实录:逆向工程最常见的六个翻车现场

用好MBG的关键不在于配置写得多花哨,而在于你知道它会在哪里翻车。下面六个场面,是我在真实项目里一个个踩出来的,每个都有代表性。

5.1 表名和列名的映射大坑

第一个坑:tableName="t_order"不写domainObjectName,生成出来的类叫TOrder。表名前缀t_全保留,看着就别扭。后来我学乖了,所有表都显式指定domainObjectName去掉前缀。

第二个坑藏在列名里。默认配置下,下划线转驼峰是有的,但MBG不会去掉列名前缀。比如f_order_sn,生成属性是fOrderSn,不是orderSn。如果团队的库表统一有前缀,生成完之后实体类全是带前缀的字段,那叫一个难看。想在配置层面解决,可以用columnRenamingRule:

<table tableName="t_order" domainObjectName="Order"> <columnRenamingRule searchString="^f_" replaceString=""/> </table>

这会把所有以f_开头的列名去掉前缀再转驼峰。不过我更推荐另一种做法:既然用了MBG,表设计阶段就应该少用前缀,列名直接order_sn、user_id,从源头消灭问题。

第三个坑是MySQL保留字。比如表里有个列叫order、status、desc,生成出来的SQL语句会直接跑错。这就是我在上一章配置文件里加反引号的原因,有保留字风险就开beginningDelimiter和endingDelimiter。

5.2 tinyint、bit、decimal的类型映射全解析

类型映射是新手最容易懵的地方。同是tinyint,数据库定义的长度不一样,生成的Java类型就不同:

MySQL类型Java类型说明
tinyint(1)Boolean适合存is_deleted、is_valid
tinyint(4)Integer适合存状态枚举
smallintInteger无歧义
bigint(20)Long主键常用
decimal(10,2)BigDecimal开forceBigDecimals后稳定
datetimeLocalDateTime开useJSR310Types后
dateLocalDate开useJSR310Types后
timestampLocalDateTime注意时区问题
bit(1)Boolean和tinyint(1)类似

这个映射关系的坑在于:你定义tinyint(1),生成Boolean,但数据库里如果某天有人把值改成2,Java端Boolean读取会处理不了。定义tinyint(4)生成Integer就比较稳。所以我建表时的原则是:只存0/1的列用tinyint(1),状态码统一用tinyint(4)或者直接用int。

再说decimal。如果没有forceBigDecimals,某些版本的MBG会把decimal映射成Double,金额计算出现0.1+0.2不等于0.3的场景够你查一晚上。项目里只要涉及金额、费率、积分这类数值,一律强制BigDecimal。

5.3 Example类到底要不要生成

这是我纠结最久的一个配置项。targetRuntime="MyBatis3"默认生成*Example类,MyBatis3Simple则完全不生成。

Example类的本质是动态SQL构造器,比如要查“用户ID为1001到1005之间、且状态不等于2的订单”,它能用链式API写出来:

OrderExample example = new OrderExample(); OrderExample.Criteria criteria = example.createCriteria(); criteria.andUserIdBetween(1001L, 1005L); criteria.andPayStatusNotEqualTo(2); example.setOrderByClause("create_time desc"); List<Order> orders = orderMapper.selectByExample(example);

简单查询确实好用,不用写XML。但它的缺点也很明显:业务复杂后Example方法嵌套多、链巨长,可读性直线下降;多表关联查询它完全帮不上忙;生成的Example类本身代码量非常大,一个表一个Example类可能比实体类还长两三倍,代码仓库里全是这种“条件拼接器”,看着心烦。

我的实际判断标准:如果模块以单表简单CRUD为主,用MyBatis3Simple,不要Example;如果模块查询条件丰富、经常变化,比如后台管理系统的列表页,保留MyBatis3和Example反而省事。不要为了代码好看牺牲开发效率,也不要为了花哨把所有表都生成Example。

5.4 重复生成的覆盖规则,别把自己坑了

这是团队协作里最严重的问题。MBG对不同的文件类型,重复生成时的行为不一样:Java文件默认直接覆盖,XML文件默认在原有基础上合并。

这意味着两种翻车方式。第一种,你手写了新的Mapper方法在接口里,比如加了一个List<Order> listByUserId(Long userId),然后重新跑生成,接口文件被整体覆盖,你写的方法直接消失。第二种,你在生成的XML里手写了SQL,比如加了一段多表查询,重跑后XML是合并的,手写SQL还在,但如果你的XML里存在标签顺序不符合MBG预期,合并过程可能生成重复的SQL片段。

我的解决方案是把生成代码和手工代码物理隔离。手写业务方法放到另一个接口里,比如OrderExtMapper,继承生成的OrderMapper,XML也单独建OrderExtMapper.xml,手工SQL全部在这里维护。再或者直接在Service层用Mapper接口加上Java 8的default方法做扩展。跑一百遍生成都不怕,手工代码永远在自己的文件里。

5.5 连接串和驱动导致的“表找不到”

MBG报“Table not found”的错,90%不是表真不存在,而是连接参数有问题。MySQL 8用老驱动com.mysql.jdbc.Driver会直接报驱动类找不到;URL不带serverTimezone会提示时区无法识别;连接串误带了sky_mall这种不存在的schema,自然找不到表。

还有一种隐蔽情况:配置文件里写schema="mall",但tableName没带schema,MBG默认在指定schem下找表,如果连接用户没权限访问这个schema,也会报找不到。我现在的习惯是连接串直接指定默认库:

jdbc.url=jdbc:mysql://localhost:3306/mall?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8

然后table节点里不写schema、不写catalog,干净利落。

5.6 换数据库时的映射差异

如果项目从MySQL迁移到PostgreSQL或Oracle,MBG的配置要跟着改三处。驱动类和连接串换成目标数据库的,javaTypeResolver的映射规则要重新核对,比如Oracle的DATE类型默认可能映射成java.sql.Timestamp,需要override成LocalDateTime;PostgreSQL的bool列虽然有官方映射,但实际使用中更多人习惯映射成Boolean而不是Character。最后,批量生成前先拿两张代表性表试跑,检查生成的XML里SQL方言是否正确,特别是分页、自增主键返回这类语法,各数据库差异巨大。

6. 进阶玩法:让生成代码真正贴合团队规范

MBG默认生成的代码符合“通用规范”,但每个团队都有自己的约定,比如启用Lombok、统一继承基类、统一字段填充逻辑。这些如果不在生成阶段处理,生成的代码落地后还要手工改一遍,价值就缩水了。

6.1 用Lombok干掉实体类噪音

默认生成的实体类每个字段后面跟着完整的getter/setter,40个字段的类能干到300行以上,一眼扫过去全是噪音。团队里如果Lombok普及率高,我建议在生成时直接让实体类带上@Data注解,删掉所有getter/setter。

标准实现是利用MBG的插件扩展点。在generatorConfig.xml的<context>里注册自定义插件,插件继承PluginAdapter,重写modelBaseRecordClassGenerated方法:

public class LombokPlugin extends PluginAdapter { @Override public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable, Context context) { topLevelClass.addImportedType("lombok.Data"); topLevelClass.addAnnotation("@Data"); return true; } @Override public boolean modelSetterMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) { return false; } @Override public boolean modelGetterMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) { return false; } }

插件类编译后所在的jar加入<plugin>的依赖里,配置里加上<plugin type="com.example.LombokPlugin"/>,重新生成后实体类变成几行注解加字段,清爽很多。如果不想写插件,也可以生成后再用脚本批量处理,但可维护性差,我首推plugin方案。

6.2 统一基类与公共字段的两种处理方式

业务表几乎都有create_time、update_time、is_deleted、version这些公共字段。MBG默认把它们都塞进实体类,生成的CRUD方法也包含这些字段,问题来了:每张表的实体类都重复定义这四个字段,维护时想改一个公共字段类型要动所有类。

我的做法是分场景处理。如果项目统一用MyBatis-Plus或自研BaseEntity体系,那就在<table>节点里用<ignoreColumn>把公共字段全部忽略,然后让生成的实体类继承BaseEntity。这个方案最干净,实体类只保留自己的业务字段。

如果不用继承体系,公共字段就留在实体类里,但我在Service层做一个统一的字段填充逻辑,封装成BaseServiceImpl,在insert前统一setCreateTime、setUpdateTime,在update前统一setUpdateTime。比每张表手工set强得多。

6.3 生成之后的二次改造:从Mapper到Service

MBG的边界到Mapper层,Service层它管不了。但业务逻辑大同小异,如果每个Service都重复写“插入前填充时间、删除时逻辑删除、查询时过滤deleted”,一样是体力活。

我自己的习惯是写一个BaseService<T>接口,定义通用的insert、updateById、getById、deleteById、pageQuery,再用一个BaseServiceImpl提供实现。生成的Mapper注入进子类,子类Service只需要声明自己的业务方法。这套体系能让新表的Service代码压到50行以内。

不过要注意,这套封装只适合“单表基础CRUD”。涉及多表、复杂查询的业务,直接在扩展Mapper里手写最可靠,不要为了偷懒硬塞进通用实现里,否则最后全是if判断腌臜逻辑。

6.4 逆向工程和手写SQL的边界

和MBG共处多年,我的最终体会就是边界感。它擅长的是单表CRUD、按主键查询、条件简单的列表查询,手写SQL擅长的是多表Join、聚合报表、窗口函数、复杂子查询。

实际项目中我是这么分层的:基础单表操作全部靠MBG生成的Mapper,遇到复杂查询就在Ext Mapper里手写XML,两者各自维护、互不干扰。代码评审时看到“在生成XML里手写多表SQL”的操作,我通常直接打回,这不是代码风格问题,而是你根本没法安全地重新生成代码。

写在最后的一些实在建议

这套玩法用了几个项目之后,我最大的体会是:逆向工程最值钱的部分不是替你打字,而是替你定规矩。列名到字段名的映射、类型到Java类型的映射、接口命名风格,它在团队里是一个隐形的规范落地器,所有人拿到的代码长得一样,换人维护时不用猜前人的命名习惯。

我现在的新项目流程基本固定:表结构评审通过后,写好generatorConfig.xml,一条Maven命令生成底层代码,手工SQL集中在扩展文件里维护,每次表结构变更重新生成一遍,其余业务代码完全不碰底座。这个流程稳定跑了一年多,基本没有在字段映射上花过排查时间。

最后再分享一个细节:不要在生产环境数据库上跑生成命令,哪怕只是读表结构也不建议,频繁连接元数据会给数据库带来不必要的压力,我习惯在本地或测试库建一份相同表结构,生成完毕再核对差异。工具是让日常开发变得安静的,不要让它变成下一个线上事故源。

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

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

立即咨询