MyBatisPlus插件原理
MyBatis作为一款优秀的持久层框架,其插件机制为开发者提供了强大的扩展能力。无论是实现SQL日志打印、分页查询,还是数据脱敏、权限控制,都可以通过插件轻松搞定。
一、MyBatis插件的本质:拦截四大核心接口
MyBatis插件的核心思想是"拦截"——在SQL执行的关键节点插入自定义逻辑。而这些关键节点,正是由MyBatis的四个核心接口及其方法构成。
为什么是这四个接口?
可以把MyBatis执行SQL的过程想象成一条"生产流水线",而这四个接口就是流水线上的四个关键工位,负责SQL执行的全流程:
核心接口 | 作用(类比工厂工位) | 核心方法 |
---|---|---|
Executor | 执行器(总指挥):负责SQL执行的整体调度,包括查询、更新、事务管理等 | query() 、update() 、commit() |
StatementHandler | SQL处理器(执行者):负责与数据库交互,创建Statement对象、执行SQL | prepare() 、parameterize() 、query() |
ParameterHandler | 参数处理器:负责给SQL中的占位符设置参数(Java对象→数据库类型) | setParameters() |
ResultSetHandler | 结果集处理器:负责将数据库返回的结果集转换为Java对象 | handleResultSets() |
这四个接口覆盖了SQL执行的完整生命周期:从接收请求、处理参数、执行SQL到转换结果。插件只有拦截这些接口的方法,才能在关键节点插入自定义逻辑。
二、动态代理:插件实现的"黑科技"
知道了要拦截哪些接口,接下来的问题是:MyBatis如何实现"拦截"?
答案是JDK动态代理。
用生活案例理解动态代理
想象你去租房的场景:
- 你(调用者)想找房东(目标对象)租房(原方法)
- 但你通过中介(代理对象)沟通,中介会先帮你筛选房源、议价(拦截逻辑)
- 最后中介再联系房东完成租房(执行原方法)
在这个过程中,你没有直接接触房东,但中介悄悄在租房流程中加入了额外操作。这就是动态代理的核心思想:不修改原对象的前提下,通过代理对象在方法执行前后插入自定义逻辑。
MyBatis中的代理实现
MyBatis插件的工作流程与租房案例高度相似:
- 定义拦截逻辑:开发者编写插件类,指定要拦截的接口和方法(比如拦截
StatementHandler
的prepare()
方法) - 创建代理对象:MyBatis启动时,会为四大接口的实现类创建代理对象(通过JDK动态代理)
- 执行拦截流程:
- 当MyBatis调用目标方法(如
statementHandler.prepare()
)时,实际调用的是代理对象的方法 - 代理对象先执行插件中的拦截逻辑(如记录SQL执行开始时间)
- 再调用原对象的方法,执行真正的SQL操作
- 最后执行插件中的后续逻辑(如计算SQL执行耗时)
- 当MyBatis调用目标方法(如
通过这种方式,插件既不会修改MyBatis的核心代码,又能灵活地插入自定义逻辑。
三、实战案例:编写一个SQL执行时间监控插件
我们通过一个实际案例感受插件的工作机制。假设我们需要监控每条SQL的执行时间,步骤如下:
1. 编写插件类(实现Interceptor接口)
@Intercepts({@Signature(type = StatementHandler.class, // 要拦截的接口method = "prepare", // 要拦截的方法args = {Connection.class, Integer.class} // 方法参数)
})
public class SqlExecutionTimePlugin implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 1. 执行拦截逻辑(记录开始时间)long startTime = System.currentTimeMillis();try {// 2. 执行原方法(让MyBatis继续处理SQL)return invocation.proceed();} finally {// 3. 执行后续逻辑(计算耗时)long endTime = System.currentTimeMillis();long cost = endTime - startTime;// 获取当前执行的SQLStatementHandler statementHandler = (StatementHandler) invocation.getTarget();BoundSql boundSql = statementHandler.getBoundSql();String sql = boundSql.getSql();System.out.println("SQL执行耗时:" + cost + "ms,SQL语句:" + sql);}}// 创建代理对象(固定写法)@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}// 设置插件属性(可在mybatis-config.xml中配置)@Overridepublic void setProperties(Properties properties) {// 可读取配置参数,如设置超时阈值等}
}
2. 配置插件(在mybatis-config.xml中注册)
<configuration><plugins><plugin interceptor="com.example.SqlExecutionTimePlugin"><!-- 可配置插件属性 --></plugin></plugins>
</configuration>
3. 执行效果
当MyBatis执行SQL时,插件会自动打印执行时间:
SQL执行耗时:15ms,SQL语句:SELECT * FROM user WHERE id = ?
四、插件开发注意事项
- 拦截范围:插件只能拦截四大核心接口的方法,不可自定义其他接口
- 性能影响:避免在插件中编写耗时操作,否则会影响SQL执行效率
- 顺序问题:多个插件时,执行顺序由配置顺序决定(先配置的先执行)
- 兼容性:过度依赖内部实现可能导致升级MyBatis时出现问题
五、总结
MyBatis插件通过"拦截四大核心接口+JDK动态代理"的方式,实现了对SQL执行流程的灵活扩展。其本质是:
- 以四大接口为切入点,覆盖SQL执行全流程
- 用动态代理机制在方法执行前后插入自定义逻辑
- 不侵入核心代码,却能实现强大的扩展功能
插件的核心不是"修改",而是"增强"——在不破坏原有逻辑的前提下,优雅地扩展功能。