MyBatis 插件
🎯 一、MyBatis 插件的本质:不是“插件”,而是“拦截器”!
很多开发者说“MyBatis 插件”,其实更准确的叫法是 Interceptor(拦截器)。
它不是像浏览器插件那样“附加功能”,而是 MyBatis 框架提供的一种 面向切面编程(AOP)能力,让你在 SQL 执行的关键节点上插入自定义逻辑。
✨ 一句话概括原理:
MyBatis 插件 = 基于 JDK 动态代理 + 责任链模式,对四大核心接口的方法进行精准拦截。
🔍 二、底层原理深度剖析(展现技术深度)
MyBatis 允许你拦截以下 四大核心接口 的方法:
| 接口 | 作用 | 可拦截场景 |
|---|---|---|
Executor | SQL 执行入口 | 审计、缓存、分页、超时控制 |
StatementHandler | 处理 PreparedStatement | SQL 改写(如多租户、分表)、SQL 注入防护 |
ParameterHandler | 设置 SQL 参数 | 参数加密、脱敏、校验 |
ResultSetHandler | 处理查询结果 | 字段脱敏、自动转换、敏感数据过滤 |
🧠 工作机制三步走:
- 声明拦截点:通过
@Intercepts({ @Signature(...) })注解指定要拦截的类、方法、参数。 - 代理包装:MyBatis 在创建上述接口实例时,调用
Plugin.wrap(target, interceptor),生成 JDK 动态代理对象。 - 执行拦截:当目标方法被调用时,先进入
intercept(Invocation invocation),你可以在invocation.proceed()前后做增强。
💡 关键洞察:
插件只对“接口”生效(因为用的是 JDK 代理),且 多个插件会形成嵌套代理链,顺序由注册先后决定。
🌟 三、为什么 MyBatis 插件值得在架构中使用?(价值升华)
它解决了传统开发中的三大痛点:
| 痛点 | 插件方案 | 优势 |
|---|---|---|
| 业务代码被横切逻辑污染 | 将审计、日志、权限等抽离到插件 | 关注点分离,代码更干净 |
| 修改 SQL 需要动所有 Mapper | 在插件中统一重写 SQL | 零侵入,一处修改,全局生效 |
| 无法监控底层 JDBC 行为 | 直接拦截 Statement/ResultSet | 比 Spring AOP 更底层、更精准 |
✅ 这就是为什么:高级工程师用插件,初级工程师改业务代码。
🚀 四、实战案例:Spring Boot + MyBatis 实现 SQL 审计插件
现在,我们来看一个 真实、可落地、有业务价值 的例子——SQL 审计插件,这在金融、政务、医疗等强监管行业是刚需!
📌 需求背景
- 记录所有 DML 操作(INSERT/UPDATE/DELETE)
- 包含:SQL 语句、实际参数、操作人、IP、执行时间、影响表名
- 敏感字段(如密码、手机号)自动脱敏
- 异步写入日志,不影响主事务性能
🛠️ 代码实现(Spring Boot 3 + MyBatis)
步骤 1:定义插件类
@Component
@Intercepts(@Signature(type = Executor.class,method = "update",args = {MappedStatement.class, Object.class}
))
public class SqlAuditInterceptor implements Interceptor {@Autowiredprivate AuditLogService auditLogService;@Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement ms = (MappedStatement) invocation.getArgs()[0];SqlCommandType cmdType = ms.getSqlCommandType();// 只审计 DMLif (!isDml(cmdType)) return invocation.proceed();BoundSql boundSql = ms.getBoundSql(invocation.getArgs()[1]);String sql = boundSql.getSql();String table = extractTableName(sql);String params = maskSensitive(boundSql.getParameterObject());long start = System.currentTimeMillis();try {Object result = invocation.proceed(); // 执行原 SQLlong cost = System.currentTimeMillis() - start;// 构建审计日志AuditLog log = new AuditLog().setSql(sql).setParams(params).setTable(table).setOperator(getCurrentUsername()).setClientIp(getClientIp()).setCostMs(cost).setCommandType(cmdType.name());auditLogService.asyncSave(log); // 异步保存return result;} catch (Exception e) {log.error("审计异常", e);throw e;}}// ... 工具方法:脱敏、取用户、取 IP、解析表名等
}步骤 2:注册插件(Spring Boot 配置)
@Configuration
@MapperScan("com.company.mapper")
public class MyBatisConfig {@Beanpublic ConfigurationCustomizer mybatisCustomizer(SqlAuditInterceptor interceptor) {return config -> config.addInterceptor(interceptor);}
}步骤 3:异步日志服务(避免阻塞)
@Service
public class AuditLogService {private static final Logger AUDIT = LoggerFactory.getLogger("AUDIT");@Asyncpublic void asyncSave(AuditLog log) {AUDIT.info("AUDIT | user={} | ip={} | table={} | sql=[{}] | params=[{}]",log.getOperator(), log.getClientIp(), log.getTable(),log.getSql(), log.getParams());}
}📊 审计日志效果示例
AUDIT | user=admin | ip=10.0.0.5 | table=user
| sql=[UPDATE user SET phone=? WHERE id=?]
| params=[{phone=138****1234, id=1001}]✅ 敏感字段自动脱敏
✅ 操作人/IP 自动采集
✅ 零侵入业务代码
✅ 异步写入,性能无损
💎 五、总结
“MyBatis 插件不是炫技,而是 工程化思维的体现。
它让我们在不修改一行业务代码的前提下,实现 系统级能力的统一管控。真正的架构能力,不在于用了多少框架,而在于能否用最克制的方式,解决最本质的问题。”
