Skip to main content

插件

原理

基于动态代理与责任链模式,对核心组件方法进行拦截。

编写步骤

  • 实现 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)。
  • 配置插件:
    • 在 mybatis-config.xml 中添加 标签,指定插件类和可选属性。
    • 或者在spring config配置

四个核心接口及其方法的执行顺序

  • 典型查询调用栈(简化):

    1. Executor.query/update
      • 检查二级缓存,如果未命中则创建StatementHandler
    2. StatementHandler.prepare
      • 创建并配置 JDBC Statement/PreparedStatement,绑定 SQL。调用parameterize方法。
    3. ParameterHandler.setParameters
      • 设置占位符参数,绑定参数到 Statement
    4. StatementHandler.query/update
      • 执行 SQL,获取 ResultSet
    5. ResultSetHandler.handleResultSets(Statement)
      • 将 ResultSet 映射为目标对象集合(映射列到属性、嵌套映射、延迟加载等)。
    6. Executor 写入本地/二级缓存(若可缓存),返回结果集合。
  • 核心接口职责速览:

    • Executor:增删改 update(...),查询 query(...)commit/rollbackcreateCacheKeyclearLocalCache,装饰实现如 CachingExecutor 负责缓存拦截。
    • StatementHandler:prepare/parameterize/batch/update/query,封装 JDBC Statement 的创建、参数化与执行。
    • ParameterHandler:getParameterObject/setParameters,负责将实参按映射设入 PreparedStatement。
    • ResultSetHandler:handleResultSets/handleOutputParameters,负责将结果集映射为对象或集合。

案例

  • 记录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) { }
}