跳到主要内容

Mybatis拦截器

核心部件

MyBatis 提供了一个插件机制,允许开发者通过拦截器(Interceptor)拦截 MyBatis 的执行流程,方便进行一些扩展功能的开发。MyBatis 可以通过拦截四类核心对象的方法,这四类对象分别是:

  1. Executor 拦截器:负责拦截 SQL 的执行流程,如查询和更新。
  2. ParameterHandler 拦截器:拦截参数设置的过程,用于处理 SQL 参数。
  3. ResultSetHandler 拦截器:拦截结果集处理,操作查询结果。
  4. StatementHandler 拦截器:拦截 SQL 语句的生成和执行。

Executor 拦截器

Executor 是 MyBatis 的执行器,它负责调用StatementHandler操作数据库,并把结果集通过ResultSetHandler进行自动映射,另外,它还处理二级缓存的操作。

拦截的常见方法有:

  • update(MappedStatement ms, Object parameter)
  • query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
  • commit(boolean required)
  • rollback(boolean required)

应用场景:

  • 事务控制:拦截 commitrollback 进行自定义的事务处理。
  • 缓存增强:自定义缓存逻辑,可以拦截 query,进行缓存层面的增强。

示例:

@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class ExecutorInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 自定义逻辑处理
return invocation.proceed();
}
}

ParameterHandler 拦截器

ParameterHandler 负责将用户传递的参数处理后,绑定到 SQL 语句中。在 SQL 执行前,MyBatis 会使用 ParameterHandler 处理参数,将其转换为 JDBC 的 PreparedStatement 需要的参数形式。

拦截的常见方法有:

  • setParameters(PreparedStatement ps)

应用场景:

  • 参数审计:对传入的参数进行检查、修改或记录。
  • 自动加密:对于需要传递敏感信息的字段,在这里可以进行加密处理。

示例:

@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})
})
public class ParameterHandlerInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 参数处理逻辑
return invocation.proceed();
}
}

ResultSetHandler 拦截器

ResultSetHandler 负责处理从数据库中查询的结果集,将其转换为 Java 对象。MyBatis 通过 ResultSetHandler 将 SQL 查询结果与映射的 Java 类进行匹配。

拦截的常见方法有:

  • handleResultSets(Statement stmt)

应用场景:

  • 数据脱敏:查询出的敏感信息,可以在这里进行脱敏处理。
  • 结果集缓存:通过拦截查询结果,可以将其缓存,以减少重复的数据库访问。

示例:

@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class ResultSetHandlerInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 结果集处理逻辑
return invocation.proceed();
}
}

StatementHandler 拦截器

StatementHandler 负责执行 SQL 语句。MyBatis 在执行 SQL 之前会生成 Statement(如 PreparedStatementCallableStatement),并将 SQL 语句发送给数据库。拦截 StatementHandler 可以对 SQL 语句进行修改,或对 SQL 执行的行为进行控制。

拦截的常见方法有:

  • prepare(Connection connection, Integer transactionTimeout)
  • parameterize(Statement stmt)
  • batch(Statement stmt)
  • update(Statement stmt)
  • query(Statement stmt, ResultHandler resultHandler)

应用场景:

  • SQL 日志打印:可以在 SQL 发送到数据库之前拦截 SQL,进行日志打印。
  • SQL 优化:拦截 prepare 方法对生成的 SQL 语句进行修改,或进行某种优化(比如自动加上分页或排序)。

示例:

@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class StatementHandlerInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// SQL 语句处理逻辑
return invocation.proceed();
}
}

MappedStatement

MappedStatement维护了一条<select|update|delete|insert>节点的封装。MappedStatement 的主要作用是将一个 SQL 语句和其相关的元信息(如参数类型、结果映射等)关联起来,供 Executor 执行。

SqlSource

负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回。

BoundSql

表示动态生成的SQL语句以及相应的参数信息。

实现步骤

标记注解Intercepts

@Intercepts 注解用于标记一个类是 MyBatis 拦截器,并指定拦截的方法和参数类型。@Signature 注解用于指定要拦截的方法签名,它通常与 @Intercepts 注解一起使用。@Signature注解用于指定拦截的方法签名。

@Signature 注解用于指定 MyBatis 拦截器需要拦截的目标,具有以下三个属性:

  1. type:指定需要拦截的接口。常见的接口包括:
    • Executor:执行器,用于执行 SQL 语句的核心接口。
    • StatementHandler:处理 SQL 语句的接口。
    • ParameterHandler:负责处理 SQL 参数的接口。
    • ResultSetHandler:处理查询结果集的接口。
  2. method:指定需要拦截的接口方法。常见的方法包括:
    • update:用于执行 INSERTUPDATEDELETE 操作的 Executor 接口方法。
    • query:用于执行 SELECT 操作的 Executor 接口方法。
    • prepare:在 StatementHandler 中,用于预编译 SQL 语句。
    • parameterize:在 ParameterHandler 中,用于设置 SQL 参数。
  3. args:指定方法参数的类型,以便 MyBatis 在执行时能够准确匹配方法。常见的参数类型包括:
    • MappedStatement:表示 SQL 映射语句的类。
    • Object:可以是查询参数、实体对象或其他上下文信息。
    • Connection:用于数据库连接。
    • RowBounds:用于分页。
    • ResultHandler:用于处理查询结果。

实现Interceptor接口

实现intercept方法:

    Object intercept(Invocation invocation) throws Throwable;

可以通过Invocation获取被代理对象target,代理方法method和方法参数args

实现plugin方法

插件用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理,可以决定是否要进行拦截进而决定要返回一个什么样的目标对象,官方提供了示例:return Plugin.wrap(target, this);,可以在这个方法中提前进行拦截对象类型判断,提高性能:

    @Override
public Object plugin(Object target) {
//只对要拦截的对象生成代理
if(target instanceof StatementHandler){
//调用插件
return Plugin.wrap(target, this);
}
return target;
}

MyBatis拦截器用到责任链模式+动态代理+反射机制; 所有可能被拦截的处理类都会生成一个代理类,如果有N个拦截器,就会有N个代理,层层生成动态代理是比较耗性能的。而且虽然能指定插件拦截的位置,但这个是在执行方法时利用反射动态判断的,初始化的时候就是简单的把拦截器插入到了所有可以拦截的地方。所以尽量不要编写不必要的拦截器。另外我们可以在调用插件的地方添加判断,只要是当前拦截器拦截的对象才进行调用,否则直接返回目标对象本身,这样可以减少反射判断的次数,提高性能。

参考资料

MyBatis 拦截器使用方法总结