插件
原理
基于动态代理与责任链模式,对核心组件方法进行拦截。
编写步骤
- 实现
org.apache.ibatis.plugin.Interceptor
接口,重写三个方法:- Object intercept(Invocation invocation):核心拦截逻辑,这里编写自定义行为。
- Object plugin(Object target):决定是否包装目标对象,通常使用 Plugin.wrap(target, this) 创建代理。
- void setProperties(Properties properties):可选,用于接收插件配置参数。
- 使用注解指定拦截点:
@Intercepts
:标注类,包含一个或多个@Signature
。@Signature
:指定拦截的类型(type)、方法(method)和参数类型(args)。- type:
- Executor(执行器,负责整体 SQL 执行)。
- ParameterHandler(参数处理器,处理 SQL 参数)。
- ResultSetHandler(结果集处理器,处理查询结果)。
- StatementHandler(语句处理器,处理 JDBC Statement)。
- type:
- 配置插件:
- 在 mybatis-config.xml 中添加
标签,指定插件类和可选属性。 - 或者在spring config配置
- 在 mybatis-config.xml 中添加
四个核心接口及其方法的 执行顺序
-
典型查询调用栈(简化):
- Executor.query/update
- 检查二级缓存,如果未命中则创建StatementHandler
- StatementHandler.prepare
- 创建并配置 JDBC Statement/PreparedStatement,绑定 SQL。调用parameterize方法。
- ParameterHandler.setParameters
- 设置占位符参数,绑定参数到 Statement
- StatementHandler.query/update
- 执行 SQL,获取 ResultSet
- ResultSetHandler.handleResultSets(Statement)
- 将 ResultSet 映射为目标对象集合(映射列到属性、嵌套映射、延迟加载等)。
- Executor 写入本地/二级缓存(若可缓存),返回结果集合。
- Executor.query/update
-
核心接口职责速览:
- Executor:增删改
update(...)
,查询query(...)
,commit/rollback
,createCacheKey
,clearLocalCache
,装饰实现如 CachingExecutor 负责缓存拦截。 - StatementHandler:
prepare/parameterize/batch/update/query
,封装 JDBC Statement 的创建、参数化与执行。 - ParameterHandler:
getParameterObject/setParameters
,负责将实参按映射设入 PreparedStatement。 - ResultSetHandler:
handleResultSets/handleOutputParameters
,负责将结果集映射为对象或集合。
- Executor:增删改
案例
- 记录SQL的执行耗时
- 拦截点:StatementHandler的prepare方法
- 避免误操作导致的UPDATE/DELETE 全表
- 拦截点:Executor的update方法,判断sql的类型
- 示例:
@Intercepts({
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update",
args = {MappedStatement.class, Object.class})
})
public class MetricInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.nanoTime();
try { return invocation.proceed(); }
finally { long costMicros = (System.nanoTime() - start) / 1000; /* 记录耗时 */ }
}
@Override
public Object plugin(Object target) { return Plugin.wrap(target, this); }
@Override
public void setProperties(Properties properties) { }
}