news 2026/10/11 8:21:28

MyBatis自动生成Mapper与XML:选型、底层原理与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatis自动生成Mapper与XML:选型、底层原理与避坑指南

如果你们项目里还在手工维护mapper目录下的 XML 文件,我劝你先停下来把这篇看完。作为一个被 MyBatis 折磨过也被它救过的人,我可以负责任地说,mybatis自动生成mapper和xml文件这件事,早该成为团队的基本习惯,而不是某个人兴起了才跑一次。本篇文章不打算只丢给你一个generatorConfig.xml就完事,我会把生成器的选型、生成结果的正确打开方式、以及生成之后你必须懂的 MyBatis 底层逻辑一起讲清楚。适合刚接触 MyBatis 的新人,也适合正在带团队、想统一数据访问层规范的 senior 开发。

先说结论:自动生成解决的是"同步问题",不是"写不写 SQL 的问题"。你依然需要懂 XML、懂动态 SQL、懂缓存和 TypeHandler,但它可以把最脏最累的 CRUD 重复劳动从你手里收走。

1. 为什么我强烈建议用生成器,而不是手写 Mapper 和 XML

1.1 手写 XML 最容易翻车的几个场景

我先讲讲自己踩过的坑,因为只有把痛点列出来,你才会理解后面每个步骤的设计逻辑。

前后端联调阶段最尴尬的事情是什么?是前端说nickname字段传过来了,后端查出来却是null。你打开 XML 一看,SELECT id, name, email FROM sys_user,而数据库里这列已经改名成nick_name了。这种问题在手工维护阶段几乎是无法避免的,因为字段改动往往发生在数据库设计阶段,而不是代码 review 阶段。你脑中记得的还是那张老表结构,代码里写的自然还是老字段。

更隐蔽的坑是 namespace 拼写错误。很多人以为 MyBatis 会帮你自动匹配 Mapper 接口,实际它只是靠namespace这个字符串来定位。一旦你在<mapper namespace="com.demo.mapper.UserMapper">里写错一个字母,运行时不报编译错误,第一次调用查询才报Invalid bound statement (not found)。这个错误信息我见过无数遍了,每次破案都要沿着接口、XML、Bean 扫描配置找一圈,最后发现就是手滑。

手写的 CRUD 还有个大麻烦:代码重复。一张表配一套insert / selectByPrimaryKey / updateByPrimaryKey / deleteByPrimaryKey,十张表就是十套差不多的 XML 段落。一开始觉得"反正复制粘贴很快",等到某天要给所有表统一加上逻辑删除条件、统一调整字符集转换时,你会发现必须逐个人肉修改几十个文件。

1.2 自动生成能管到什么程度

MyBatis Generator(MBG)这类工具的定位很简单:读取数据库字典信息,把表和代码之间的映射关系一次性建立起来。它通过 JDBC 拿到表的字段名、字段类型、主键信息,然后生成四样东西:实体类、Mapper 接口、XML 文件、以及一个动态查询对象(MBG 里的UserExample,也就是 MyBatis-Plus 出现之前最常用的条件构造器)。

这里要澄清一个很常见的误解:自动生成的 XML 并不是"不需要看"的。它的BaseResultMap、Base_Column_List、selectByExample这些段落,仍然需要你能够看懂、敢于修改。生成器只是保证"字段名称、类型映射、主键策略"这些基础信息不会出错,复杂业务 SQL 依然要你在后面手工追加。所以我更愿意叫它"半自动生成"——骨架由机器负责,血肉由人负责。

另外还有一个思路是通用 Mapper(tk.mybatis 这类)。它的理念是去掉大部分 XML,只保留 Mapper 接口,CRUD 方法全由框架根据实体类反射生成。好处是 XML 文件数量骤减,坏处是项目对框架的侵入度变高,遇到连表查询和复杂动态条件时还是要回到 XML 的老路上。选它还是选 MBG,取决于团队对 MyBatis 的掌控力,这个后面细说。

2. 三条主流生成路线怎么选:MBG、MyBatis-Plus 生成器与 IDE 插件

2.1 MyBatis Generator:老牌流程,胜在稳定可定制

MBG 是目前使用面最广的生成工具。它可以通过 Maven 插件、Java 编码、命令行三种方式运行,最常见的是 Maven 插件方式。先准备一份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> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/demo?useSSL=false&amp;serverTimezone=Asia/Shanghai" userId="root" password="123456"/> <javaModelGenerator targetPackage="com.demo.entity" targetProject="src/main/java"/> <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"/> <javaClientGenerator type="XMLMAPPER" targetPackage="com.demo.mapper" targetProject="src/main/java"/> <table tableName="sys_user"/> </generatorConfiguration>

然后执行一条命令:

mvn mybatis-generator:generate

运行完你会发现sys_user表对应的实体、SysUserMapper、SysUserMapper.xml全部出现。MBG 生成的 XML 有个特点:它把列清单抽象成<sql id="Base_Column_List">,把字段映射抽象成<resultMap id="BaseResultMap">,然后所有 SQL 都基于这两个片段拼装。这样改一次列定义,所有查询都会同步生效。

MBG 最大的优势是可定制性强。它的每一个生成环节都预留了接口,比如注释生成器、Java 类型解析器、SQL 片段生成器,都能替换成自己的实现。如果你所在团队建过一套"统一字段注释、统一日志注解"的规范,MBG 完全可以把它固化进生成流程。

2.2 MyBatis-Plus 代码生成器:新工程脚手架的高频选择

MyBatis-Plus 生态里也有一个代码生成器(mybatis-plus-generator),和 MBG 最大的区别是:它不是只生成 Mapper 和 XML,而是连 Service、ServiceImpl、Controller、DTO 一起生成,直接给你搭好一个分层脚手架。用起来比 MBG 简洁不少:

FastAutoGenerator.create("jdbc:mysql://localhost:3306/demo", "root", "123456") .globalConfig(builder -> builder.author("yourname") .outputDir("src/main/java")) .packageConfig(builder -> builder.parent("com.demo")) .strategyConfig(builder -> builder.addInclude("sys_user")) .execute();

跑完这一小段,项目里立刻多出完整的SysUser、SysUserService、SysUserServiceImpl、SysUserController。在开发后台管理这类 CRUD 密集型应用时,这个生成器比 MBG 爽太多。

但是请注意,MyBatis-Plus 生成器默认带的是BaseMapper体系,它和原生 MyBatis 的 Mapper 写法并不完全等价。BaseMapper的selectList(null)方法直接就能查询全表,你反而不太需要 XML;但一旦要用limit、联表、复杂foreach,你还是得回到 XML 里写。我的建议是:新工程如果确定使用 MyBatis-Plus,就用它的生成器;老项目如果是原生 MyBatis 或者想保持持久层框架中立,老老实实用 MBG。

2.3 IDE 插件路线:图形点击背后隐藏的团队问题

用 IntelliJ IDEA 的MyBatisX或Free MyBatis Plugin插件,也可以从数据库面板直接生成代码。IDEA 社区版从插件市场搜索安装 MyBatisX 通常没问题,内网环境需要手动下 zip 导入时稍微麻烦一点。

插件的优点很直观:在数据库表上右键,点几下就生成完了,而且自带 Mapper 方法到 XML 节点的跳转、SQL 高亮。我自己的开发习惯是,无论用不用生成器,都会装一个 MyBatisX,因为光凭"点击方法跳转到对应<select>标签"这个功能,review 代码时能省很多时间。

但作为团队方案,我不推荐把插件作为唯一生成通道。原因在于不可复现。A 同事用插件生成了带注释的实体,B 同事在自己电脑上生成时插件版本不同,生成的代码风格就对不上了。配置文件形式的生成器可以提交到 Git 仓库,团队所有人共用一份generatorConfig.xml,这才是可以治理的流程。插件适合个人开发、快速验证,不适合团队基线的统一。

2.4 三条路线怎么选:一张对比表把账算清楚

路线适用场景生成物团队复用性定制成本
MyBatis Generator原生 MyBatis 项目、老项目迁移、需要精细控制 XML实体、Mapper、XML、Example高,配置文件入 Git 即可中,需要看文档写扩展类
MyBatis-Plus 生成器新工程、CRUD 密集、全面拥抱 MP实体、Mapper、Service、Controller、XML高,DSL 代码可沉淀低
IDE 插件快速原型、个人开发、临时建表同 MBG 但风格受插件版本影响低,不同同事生成结果不稳定低

我的取舍标准很简单:如果团队里没人能一句话说清Example怎么用,就别用 MBG;如果团队明确不引入 MyBatis-Plus,就别硬上 MP 生成器。工具要和团队的认知基线对齐,否则生成的代码只是换一种方式给别人添堵。

3. 生成的 XML 为什么长那样:解开 MyBatis 解析与 ResultMap 的底层逻辑

3.1 从 mybatis-config.xml 到 MappedStatement 的初始化链路

很多人在面对"为什么 XML 这样写就能被 MyBatis 找到"这个问题时,答案是"框架帮我处理了"——这不够。把初始化链路理清楚,对排查Invalid bound statement、找不到 typeHandler 这类问题有直接帮助。

第一步,MyBatis 启动时读取mybatis-config.xml,这个工作由XMLConfigBuilder完成。它内部用XPathParser按 XPath 路径解析配置节点,产出全局唯一的Configuration对象。Configuration是 MyBatis 的心脏:所有 Mapper 注册、MappedStatement 缓存、TypeAlias、TypeHandler 都挂在它身上。

第二步,拿到 Mapper XML 文件列表后,MyBatis 逐文件调用XMLMapperBuilder。它会先检查 namespace 对应的 Mapper 接口是否存在,然后把<resultMap>、<cache>、<select>、<insert>、<update>、<delete>等节点逐个解析。其中 SQL 节点的解析交给XMLStatementBuilder,动态 SQL 标签(<if>、<where>、<foreach>)再由XMLScriptBuilder转成 SQL 片段对象,最终封装成MappedStatement存进Configuration.mappedStatements。

第三步,当你调用userMapper.selectByPrimaryKey(1L)时,实际执行的是MapperProxy动态代理。MyBatis 用MapperProxyFactory给 Mapper 接口创建代理对象,代理逻辑里拼出 key:com.demo.mapper.SysUserMapper.selectByPrimaryKey,然后去Configuration.mappedStatements里查,找到对应的MappedStatement交给 SqlSession 执行。

这条链路里最容易出问题的就是第二步和第三步的衔接:如果 XML 里 namespace 和接口全限定名不一致,第二步解析出的 key 就和第三步对不上,于是报Invalid bound statement。生成器因为用的是同一个类名和包名,天然规避了这个错误,这也是它值得用的原因之一。

3.2 生成的 ResultMap 为什么经常需要二次调整

MBG 生成的BaseResultMap是保守的:它把表的每一列都映射一遍,同时指定了jdbcType。例如:

<resultMap id="BaseResultMap" type="com.demo.entity.SysUser"> <id column="id" property="id" jdbcType="BIGINT"/> <result column="user_name" property="userName" jdbcType="VARCHAR"/> <result column="profile_json" property="profileJson" jdbcType="LONGVARCHAR"/> </resultMap>

单表查询时,这个结果映射是完全够用的。但只要一涉及联表,它就会成为累赘。典型的场景是:查询用户和角色的JOIN,你希望结果里带一个roleName字段。此时你可以在实体类里加一个不属于表结构的roleName属性,再手写<resultMap id="UserRoleMap" type="com.demo.entity.SysUser" extends="BaseResultMap">,把额外字段映射加进去。而不是在BaseResultMap里硬加,因为一旦重新生成代码,手工改动会被覆盖。

另一个值得注意的点是autoMapping。MyBatis 默认开着自动映射,也就是说即使<resultMap>里没有显式配置某些列,只要实体类里有同名属性,也能自动映射。早期项目里有人为了让映射"完全可控"把autoMappingBehavior设为NONE,随后就会发现一堆字段查出来是 null。我的建议是:保持默认的PARTIAL,让自动映射处理简单字段,让ResultMap处理字段名差异和复杂类型。

3.3 parameterType、#{}与${}的匹配关系要注意什么

生成器生成的selectByPrimaryKey里,参数声明通常是:

<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap"> select <include refid="Base_Column_List"/> from sys_user where id = #{id,jdbcType=BIGINT} </select>

这里的parameterType在 MyBatis 3.5 之后已经可以省略,它更多是给框架一个"按类型注册 typeHandler"的线索。真正决定参数能否被正确解析的是#{id}中的属性名。当方法只有一个参数时,MyBatis 允许不写@Param;但一旦方法有多个参数,比如selectByCondition(String name, Integer status),MyBatis 就不再认识name这个属性,dict 默认会把它转成param1、param2。所以生成器在多参数方法上会主动加@Param("name") @Param("status"),这不是画蛇添足,而是 MyBatis 官方推荐的写法。

${}则是直接字符串拼接,存在 SQL 注入风险。我见过有人为了排序字段动态可控,在 XML 里写order by ${sortColumn},然后把sortColumn直接暴露给前端传参。这是非常危险的。如果非要动态排序,至少要做白名单校验:只允许id、create_time等固定字段,用<if>或者 Java 侧做枚举映射,而不是直接把参数拼进去。

4. 生成完先别急着写业务:格式化、批量操作、TypeHandler 这些坑逐个清

4.1 IDEA 社区版下 XML 文件的格式化与高亮设置

很多用社区版 IDEA 的人会发现,一保存 XML 文件,格式就被"自动整理"成自己不想要的样子;或者写 MyBatis 动态 SQL 时没有高亮,像在看纯文本。

先解决格式化问题。社区版同样支持File | Settings | Editor | Code Style | XML,把"Reformat on save"相关选项关掉,就能避免保存时强制重排。如果你希望某个文件始终不参与格式化,可以在项目根目录放一个.editorconfig:

[*.xml] indent_size = 4 max_line_length = 120

同时在 IDEA 的Editor | Code Style | XML | General里勾选"使用 EditorConfig"。这样团队里无论谁打开同一个 XML,缩进风格都是一致的,生成器输出的格式也不会被 IDE 擅自改得面目全非。

高亮问题则需要插件参与。MyBatisX提供的 SQL 高亮和#{}参数提示是免费的,社区版可以直接安装。装上之后,XML 里的 SQL 语句会按真实 SQL 语法着色,写错的表名和列名也有提示。别小看这个体验,手写复杂查询时,高亮能帮你立刻发现 SQL 语法断裂的位置。

4.2 批量 insert/update:生成器默认不给,得自己补模板

MBG 生成的单条insert和insertSelective已经够用,但批量插入它默认不生成。生产环境里一次性插入几百条用户数据,一条条调insert不仅网络往返多,事务边界也难控制。MySQL 下的批量插入标准写法是:

<insert id="batchInsert"> insert into sys_user (id, user_name, status) values <foreach collection="list" item="item" separator=","> (#{item.id}, #{item.userName}, #{item.status}) </foreach> </insert>

这里有个容易被忽略的问题:foreach拼出的 SQL 很长,如果单条数据里包含大字段,MySQL 服务端的max_allowed_packet可能被撑爆。解决思路是分片提交,Java 侧每 300~500 条分批执行一次,而不是一股脑全扔进去。

批量更新的套路则完全不同。逐条update性能差,一次update ... case when可以合并参与更新:

<update id="batchUpdateStatus"> update sys_user set status = <foreach collection="list" item="item" separator=" " open="case id" close="end"> when #{item.id} then #{item.status} </foreach> where id in <foreach collection="list" item="item" open="(" separator="," close=")"> #{item.id} </foreach> </update>

这种写法在 MySQL 和 PostgreSQL 下都能跑通,逻辑也很直观。注意每个when后面不要漏掉then,否则 SQL 拼接完成后语法错得极其隐蔽。

4.3 TypeHandler:生成器照顾不到的类型映射

实体类里的String对应数据库的varchar没什么争议,但 JSON、数组、枚举这些类型,生成器只会按 JDBC 默认类型处理,结果往往不理想。

举个例子,用户表里有个profile_json字段,数据库类型是json,生成出来的实体属性是String。如果你只是把它当作字符串存取,倒也能跑。但如果你要直接存一个 Java 对象,让 MyBatis 自动做 JSON 序列化,就需要自定义JsonTypeHandler。在 XML 里显式声明:

<resultMap id="BaseResultMap" type="com.demo.entity.SysUser"> <result column="profile_json" property="profileJson" jdbcType="OTHER" typeHandler="com.demo.handler.JsonTypeHandler"/> </resultMap>

插入时也要显式指定 handler:

insert into sys_user (..., profile_json) values (..., #{profileJson,jdbcType=OTHER,typeHandler=com.demo.handler.JsonTypeHandler})

如果你嫌每个字段都要写太麻烦,可以在 mybatis-config.xml 里全局注册:

<typeHandlers> <typeHandler handler="com.demo.handler.JsonTypeHandler" javaType="com.demo.model.UserProfile"/> </typeHandlers>

全局注册之后,只要实体属性类型是UserProfile,MyBatis 会自动选择这个 handler,不需要在 XML 里重复指定。我的经验是:不要把全局注册的类型范围放得太大,只针对你自己的业务基类做注册,避免和框架内置类型冲突。

5. 进阶玩法:让生成器按你的规矩工作,并顺手搞定日志与缓存

5.1 自定义 CommentGenerator:把字段注释同步到 JavaDoc

MBG 默认生成的实体类注释是"类名 + 作者 + 日期"拼出来的,字段注释则是直接从数据库注释复制过来的——默认效果其实能用,但风格比较"工程化"。如果你的团队要求代码里的 JavaDoc 带上字段类型、是否主键、是否逻辑删除等信息,就要自己实现注释器。

一个最小的自定义注释器可以继承DefaultCommentGenerator:

public class CustomCommentGenerator extends DefaultCommentGenerator { @Override public void addFieldComment(Field field, IntrospectedTable introspectedTable) { IntrospectedColumn column = introspectedTable.getColumn(field.getName()); if (column != null) { field.addJavaDocLine("/**"); field.addJavaDocLine(" * " + column.getRemarks()); field.addJavaDocLine(" */"); } } @Override public void addClassComment(InnerClass innerClass, IntrospectedTable introspectedTable) { innerClass.addJavaDocLine("/**"); innerClass.addJavaDocLine(" * 数据表:" + introspectedTable.getFullyQualifiedTable()); innerClass.addJavaDocLine(" */"); } }

然后在generatorConfig.xml里用<commentGenerator type="com.demo.generator.CustomCommentGenerator"/>替代默认实现。重新生成之后,实体类里的注释就和数据库字典对得上了。这个做法特别适合对接数据治理需求:数据库字段注释是产品定的,JavaDoc 从数据库带出来,代码 review 的人不需要再翻数据库文档。

5.2 SQL 打印与 MyBatis 运行时配置联动

生成代码之后想确认 SQL 是否按预期执行,最简单的方式是打开 MyBatis 的日志输出。Spring Boot 项目里配置:

mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

注意log-impl是强项,但输出格式是 MyBatis 自带的调试格式,没有 SQL 执行耗时分析。生产环境建议换成slf4j+p6spy这类工具,把 SQL 和参数分开记录,方便追踪慢查询。我自己最常用的方案是:开发环境开StdOutImpl,测试和生产环境关掉,避免日志量爆炸和敏感 SQL 外泄。

另一个和运行时相关的配置是驼峰映射。如果数据库表列名是user_name,实体属性是userName,生成器会帮你建好映射,但如果你自己手写 SQL 时列名直接返回user_name,MyBatis 默认不会把下划线自动转驼峰。建议在配置里打开:

mybatis: configuration: map-underscore-to-camel-case: true

这样手写 SQL 返回的user_name也能自动落到userName属性上,省去大量as别名。

5.3 缓存配置:二级缓存和生成代码的关系

MBG 默认不会给 XML 加<cache>标签,但 MyBatis 对二级缓存的支持就是靠这个标签开启的。直接生成 XML 后,如果你需要给某个 Mapper 开启二级缓存,可以在文件顶部手动加:

<cache eviction="LRU" flushInterval="60000" size="512" readOnly="false"/>

这里的四个参数说明:eviction控制清理策略(LRU、FIFO、SOFT、WEAK),flushInterval是刷新周期毫秒数,size是缓存对象个数上限,readOnly如果为true,缓存对象直接返回给客户端,速度快但不能修改;为false时会做序列化拷贝,更安全但开销更大。

关于二级缓存,我的立场很明确:单机应用小表可以开,分布式环境尽量不要开。因为二级缓存默认存在每个应用节点的本地内存里,多个节点之间没有同步机制,数据一致性完全依赖flushInterval,弄不好就把脏数据发给用户了。更合适的分布式缓存方案是用 Redis 做传统布局,MyBatis 的二级缓存走自定义Cache接口接入 Redis,但这个复杂度对大部分业务系统来说都是过度设计。我的建议是:先用好一级缓存,二级缓存只在报表类、几乎不更新的配置表上开。

5.4 换数据库方言时,生成器需要调整什么

自动生成不是"写死"在某一种数据库上的,只要 JDBC 驱动支持,MBG 就能连上去读表结构。我实际迁移过项目从 MySQL 到 PostgreSQL,以及一些基于 PostgreSQL 内核的国产数据库,这里说几个痛点。

第一个是连接 URL 和驱动类不同,这个改一下配置就行。第二个是大小写问题,MySQL 里表名不区分大小写,PostgreSQL 里未加引号的表名会被折叠成小写。如果原来的表建成了大写驼峰,生成结果容易出现relation "xx" does not exist。解决方法是连接 URL 加stringtype=unspecified或者在建表时统一小写下划线命名。第三个是主键自增策略,MySQL 用AUTO_INCREMENT,PostgreSQL 和国产数据库多用IDENTITY或序列。MBG 的<generatedKey>配置要跟着数据库类型走,否则插入后拿不到自增主键回填。

分页方言则是另一个大坑。MySQL 的limit在 PostgreSQL 里也能用,但 Oracle/达梦这类数据库要ROWNUM或新的FETCH FIRST。MBG 生成的单表 SQL 一般不涉及分页,但一旦你手工加了selectByPage,建议直接用分页插件(PageHelper 或 MyBatis-Plus 分页插件),而不是在各种 SQL 里手写方言。插件内部会根据当前数据库方言自动改写 SQL,这也顺手解决了切换数据库时的大量回归测试。

6. 面试官眼中的 Mapper 与 XML:生成代码之外必须具备的底层认知

6.1 MapperProxy 动态代理和 namespace 绑定

为什么SysUserMapper只是一个接口,没有实现类,调用selectByPrimaryKey却能被正确执行?这是 MyBatis 面试里最高频的题。答案藏在SqlSession的getMapper方法里:MyBatis 使用 JDK 动态代理,为每个 Mapper 接口生成一个MapperProxy实例。当你调用接口方法时,代理的invoke方法会去Configuration里按接口全限定名.方法名查找对应的MappedStatement,再交给 DefaultSqlSession 执行。

理解这一点之后,遇到Invalid bound statement (not found)就能条件反射地想到三处排查点:第一,接口的全限定名和 XML namespace 是否一致;第二,接口方法名和 XML 里的id是否一致;第三,XML 有没有被 MapperScan 扫描到。

生成器替你把这些一致性都保证了。但如果你手工复制粘贴一个 Mapper 接口,很容易忘改方法名。这也是为什么我总建议,新表接入至少跑一次生成器,而不是手写基础 CRUD。

6.2 一级缓存与二级缓存的作用域

一级缓存是SqlSession级别的。同一个SqlSession里执行同一条 SQL,第二次会直接命中缓存,不会真正查库。但要注意,任何insert/update/delete都会清空这个 SqlSession 的一级缓存,所以"先查后改再查"这种模式不会拿到脏数据。在 Spring 管理的场景里,每次 Mapper 方法通常对应一个短生命周期 SqlSession,一级缓存带来的收益远没有手动管理长事务那么明显。

二级缓存是 Mapper 级别的,也就是前面说的<cache>标签。面试时可以从这三个维度展开讲:作用域是 namespace、跨 SqlSession、基于序列化拷贝。然后补充一句"分布式环境下默认不适用,需要接入统一缓存中间件"显得更懂工程。

6.3 "面向切面在 Mapper 层改数据"这个思路怎么落地

热词里"怎么用面向切面的方式只在 Mapper 层改数据"——其实就是用 AOP 拦截 Mapper 接口方法,做数据变更的统一处理。思路完全可行,Spring AOP 可以切到 Mapper 接口上,因为 Mapper 实例本身是代理对象。做法是定义一个注解,比如@DataAudit,标记到需要审计的接口方法上,然后写一个@Aspect切面,在@Around里记录入参、执行结果、涉及的表和主键。

这里有一个细节:AOP 切的是接口方法,但 MyBatis 的 Mapper 代理对象最终执行 SQL 时走的是MapperProxy,所以切面逻辑和 SQL 执行是两层代理叠加,顺序取决于 AOP 配置和 MyBatis 代理创建顺序。如果切面里要读取 Mapper 方法的@Param值,注意入参可能是单个对象也可能是数组,需要统一封装一下。

这种做法的典型应用是:多租户数据自动补全、创建人/更新人自动填充、数据变更流水记录。比在 XML 里每张表手写created_by更新逻辑要优雅得多。

6.4 生成代码之后必须做的 Review 清单

最后整理一下我认为"生成完成不等于收工"的点:

  • git diff里出现大量文件时,先看有没有改了不该改的内容,尤其是pom.xml或自动生成的配置。
  • 检查实体类里主键字段是否用了正确的@Id或@TableId策略,不同数据库的主键生成方式不同。
  • 确认 XML 的insert是否包含自增主键回填,通常需要<selectKey>或useGeneratedKeys。
  • 将生成目录和手工目录分开,比如生成的 XML 放在resources/generated/mapper,手工扩展 SQL 放在resources/custom/mapper。这样重新生成时不会覆盖手工代码。
  • 如果团队统一用 MyBatis-Plus,把生成器跑进 CI 流水线并不难,但一定要把生成器的版本和配置固定提交,保证任何人任何时间生成的产物一致。

我自己每次跑完生成器,都会先git add整个生成结果,然后用git diff --cached --stat扫一眼改动范围。生成器是很好的工人,但它不理解你的表设计意图。用它的正确姿势,是把它当一把称手的扳手,而不是一个替你做决定的设计师。把生成流程固化好、把底层解析逻辑吃透,MyBatis 对你的价值会从"能跑"变成"可控"。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 8:20:09

云平台矩阵:跨浏览器测试的自动化方案与实践

做前端的人最怕听到一句话&#xff1a;“我这边浏览器打开是好的啊。”用户不会告诉你他用的是哪个浏览器哪个版本&#xff0c;也不会告诉你是在Windows上还是在MacBook上&#xff0c;更不会告诉你屏幕是多宽。跨浏览器测试这件事&#xff0c;说得实在一点&#xff0c;就是一场…

作者头像 李华
网站建设 2026/10/11 8:18:47

【STM32C5教程】01. STM32CubeMX2 安装与STM32C5闪灯

欢迎关注 youcansxidian【嵌入式软件AI编程】专栏 【STM32C5教程】01. STM32CubeMX2 安装与STM32C5闪灯 1. 引言 STM32CubeMX2 是 ST 推出的新一代图形化配置工具。与旧版 CubeMX 相比&#xff0c;它最显著的变化是原生支持 CMake 工程和 VS Code 工作流&#xff0c;生成速度也…

作者头像 李华
网站建设 2026/10/11 8:17:34

极域卸载工具JyTeacherUnTools:彻底清除卸载残留的完整方案

简介&#xff1a;极域完全卸载工具JyTeacherUnTools是一款专门面向学校机房管理员、信息技术教师和运维人员的极域教育软件卸载组件。它用于彻底移除极域电子教室及JyTeacherTools在系统中留下的注册表项、配置文件、临时数据和动态链接库残件&#xff0c;避免因卸载不净而出现…

作者头像 李华
网站建设 2026/10/11 8:17:32

Antigravity Awesome Skills:AI编程助手技能库安装与实战指南

1. 为什么说“技能库”和“提示词合集”完全是两回事很多人看到 Antigravity Awesome Skills 的第一反应是&#xff1a;“这不就是一个收藏了大量提示词的仓库吗&#xff1f;”如果你也有这种想法&#xff0c;大概率是还没真正理解这类项目的运作方式。Antigravity Awesome Ski…

作者头像 李华
网站建设 2026/10/11 8:16:46

成本控制工程学:从挣值管理到成本偏差的系统方法论

很多年以前&#xff0c;我第一次带项目&#xff0c;就栽在成本上。当时公司给了一个设备改造项目&#xff0c;预算五百万&#xff0c;我心里盘算着怎么也能剩下几十万。结果项目收尾一核算&#xff0c;超支一百二十多万。复盘会上我把所有报表翻了个底朝天&#xff0c;发现没有…

作者头像 李华
网站建设 2026/10/11 8:14:26

S7-300与组态王实现餐盘清洗机自动化控制方案解析

1. 为什么餐盘清洗机需要一个真正的控制系统餐盘清洗这活儿&#xff0c;看起来就是个喷水加传送带的事&#xff0c;但真在餐饮后勤、中央厨房、学校食堂干过的人都知道&#xff0c;事情远没那么简单。我接手过好几套这类设备的改造项目&#xff0c;早期那些简陋的半自动机型&am…

作者头像 李华