[扩展] 进阶映射与自定义增强
[扩展] 进阶映射与自定义增强
Prorise第六章:[扩展] 进阶映射与自定义增强
摘要: Mybatis-Plus (MP) 的强大不仅在于其内置功能,更在于其卓越的 扩展能力。本章我们将跳出常规的 CRUD,深入 MP 的底层机制。我们将首先解决 Java 丰富类型与数据库贫乏类型 的映射难题(枚举与 JSON);接着,我们将解锁 MP 的 SQL 注入器,通过自定义通用方法实现真正的“批量插入”;最后,我们将构建 安全与观测 防线,保障生产环境的代码质量。
本章学习路径
- 高级映射:利用
@EnumValue和TypeHandler,实现枚举与 JSON 的自动化持久化,告别手动转换。 - 性能观测:集成 P6Spy,捕捉带有真实参数的 SQL,精准定位慢查询。
- 安全底座:配置 BlockAttack 插件,从底层拦截全表更新/删除操作,防止“删库跑路”。
- 底层扩展:自定义 SQL 注入器,扩展
BaseMapper的能力,实现高性能的insertBatch。
6.1. 高级类型映射
痛点背景:
Java 的世界是面向对象的,我们有 GenderEnum、Map<String, Object> 等丰富的类型;而数据库的世界是扁平的,通常只有 TINYINT、VARCHAR。在传统开发中,我们需要手动转换:存的时候把枚举转 int,取的时候把 int 转枚举;存 JSON 时手动序列化,取时手动反序列化。这种重复劳动不仅低效,还容易产生“脏数据”。
MP 提供了优雅的 自动化映射机制,帮我们在两个世界间架起桥梁。
6.1.1. 通用枚举处理 (@EnumValue)
场景:数据库性别字段存的是 1 或 2,但 Java 业务代码中我们希望直接操作 GenderEnum.MALE,而不是魔法数字。
步骤 1:定义枚举类
我们需要一个实现了“数据值”与“展示值”绑定的枚举。
文件路径: src/main/java/com/example/mpstudy/domain/enums/GenderEnum.java
1 | package com.example.mpstudy.domain.enums; |
步骤 2:实体类引用
直接将字段类型定义为枚举,无需任何额外的 XML 配置。
文件路径: src/main/java/com/example/mpstudy/domain/UserDO.java
1 |
|
步骤 3:验证效果
1 |
|
6.1.2. JSON 自动映射 (JacksonTypeHandler)
场景:MySQL 5.7+ 支持 JSON 类型。我们希望将 Java 的 Map 或 List 直接存入数据库的 JSON 字段,取出时自动转回对象。
步骤 1:全局注册 TypeHandler
为了避免在每个字段上都写 @TableField(typeHandler=...),我们推荐使用配置自动扫描。
文件路径: src/main/resources/application.yml
1 | mybatis-plus: |
步骤 2:配置实体类(关键!)
这里有一个新手必踩的坑:必须开启 autoResultMap。
文件路径: src/main/java/com/example/mpstudy/domain/UserDO.java
1 | // [核心] autoResultMap = true |
步骤 3:验证效果
1 |
|
6.2. 安全与观测:给系统装上监控
在生产环境中,代码的健壮性和可观测性往往比功能实现更重要。
6.2.1. SQL 性能透视镜 (P6Spy)
MyBatis 默认的控制台日志只打印 SQL 模板(WHERE id = ?)和参数,无法显示 真实执行 SQL 和 耗时。这让我们难以直接复制 SQL 去数据库排查慢查询。P6Spy 是一个 JDBC 代理,能截获并统计 SQL。
配置步骤:
1. 引入依赖 & 开启配置
(在上一章的动态数据源配置中,我们已经设置了 p6spy: true。如果是单数据源,需引入 p6spy 依赖并修改 driver 为 com.p6spy.engine.spy.P6SpyDriver)
2. 精细化日志格式
在 src/main/resources 下新建 spy.properties:
1 | # 1. 使用 MP 团队提供的单行日志格式(方便 grep 和阅读) |
运行效果:
1 | 2025-08-23 12:00:00 | took 15ms | statement | SELECT * FROM tb_user WHERE id = 1 |
- took 15ms:这是最关键的指标。如果某个简单的查询耗时超过 500ms,你就需要警惕了。
- statement:这是可以直接复制到 Navicat 执行的完整 SQL。
6.3.2. 删库跑路防御盾 (BlockAttack)
风险:开发人员在写更新语句时,不小心传了一个 null 的 Wrapper:userMapper.update(user, null);
这会生成 UPDATE tb_user SET ...(无 WHERE 条件),导致全表数据被覆盖!
解决方案:配置 BlockAttackInnerInterceptor。它是 MP 提供的一个 SQL 分析拦截器,如果发现 Update/Delete 语句没有 Where 条件,直接抛出异常。
配置代码:
文件路径: src/main/java/com/example/mpstudy/config/MybatisPlusConfig.java
1 |
|
验证测试:
1 |
|
结果:抛出 Prohibition of table update operation 异常,SQL 被拦截,未发送到数据库。
6.3. [高阶] 自定义 SQL 注入器
痛点背景:
MP 的 IService.saveBatch 方法虽然方便,但它本质上是在 for 循环中执行单条 INSERT(或者基于 JDBC 的 Batch 重写),在数据量极大时性能依然有瓶颈。
MySQL 原生支持 INSERT INTO values (),(),() 语法,性能极高。MP 内置了这个方法(insertBatchSomeColumn),但默认没有注入到 BaseMapper 中。本节我们将通过 扩展 SQL 注入器,把它加进去。
6.3.1. 创建自定义 BaseMapper
我们需要定义一个新的 Mapper 父类,包含我们想要扩展的方法。
文件路径: src/main/java/com/example/mpstudy/base/MyBaseMapper.java
1 | package com.example.mpstudy.base; |
6.3.2. 配置 SQL 注入器
我们需要告诉 MP,启动时除了加载默认的 CRUD 方法,还要把我们的 insertBatchSomeColumn 加载进去。
文件路径: src/main/java/com/example/mpstudy/config/MySqlInjector.java
1 | package com.example.mpstudy.config; |
6.3.3. 业务 Mapper 继承新父类
修改 UserMapper,让它继承 MyBaseMapper 而不是 BaseMapper。
文件路径: src/main/java/com/example/mpstudy/mapper/UserMapper.java
1 | // 改为继承 MyBaseMapper |
6.3.4. 性能对比测试
文件路径: src/test/java/com/example/mpstudy/BatchInsertTest.java
1 |
|
1
2
-- ==> Preparing: INSERT INTO tb_user (name, age) VALUES (?, ?), (?, ?), (?, ?)...
-- 1000 条数据拼接在了一条 SQL 中发送,数据库 IO 只有一次。
6.4. 本章总结与进阶速查
6.4.1. 场景化速查
场景一:存储复杂 JSON 对象
- 方案:JacksonTypeHandler
- 关键点:
@TableName(autoResultMap = true)必须加,否则查出来是 null。
场景二:海量数据写入
- 方案:扩展
InsertBatchSomeColumn - 关键点:不要用
IService.saveBatch(伪批量),要用自定义注入器实现的真批量。 - 限制:仅适用于 MySQL 等支持多值 Insert 语法的数据库。
场景三:排查慢 SQL
- 方案:P6Spy
- 操作:看日志中的
took xx ms,定位耗时源头。
6.4.2. 核心避坑指南
JSON 查不到数据
- 现象:数据库有 JSON,Java 里的 Map 是 null。
- 原因:99% 是因为没加
@TableName(autoResultMap = true)。MyBatis 默认的 ResultMap 不会处理 TypeHandler,必须开启自动构建。
枚举插入报错
- 现象:
Data truncated for column 'gender'。 - 原因:数据库字段长度不够,或者枚举的
@EnumValue值与数据库类型不匹配(如枚举是 String,库是 Int)。
- 现象:
自定义注入器不生效
- 原因:
MySqlInjector类忘记加@Component注解,导致 Spring 没扫描到它。
- 原因:







