Mybatis拦截器
核心部件
MyBatis 提供了一个插件机制,允许开发者通过拦截器(Interceptor)拦截 MyBatis 的执行流程,方便进行一些扩展功能的开发。MyBatis 可以通过拦截四类核心对象的方法,这四类对象分别是:
- Executor 拦截器:负责拦截 SQL 的执行流程,如查询和更新。
- ParameterHandler 拦截器:拦截参数设置的过程,用于处理 SQL 参数。
- ResultSetHandler 拦截器:拦截结果集处理,操作查询结果。
- 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)
应用场景:
- 事务控制:拦截
commit
或rollback
进行自定义的事务处理。 - 缓存增强:自定义缓存逻辑,可以拦截
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
(如 PreparedStatement
或 CallableStatement
),并将 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 拦截器需要拦截的目标,具有以下三个属性:
type
:指定需要拦截的接口。常见的接口包括:Executor
:执行器,用于执行 SQL 语句的核心接口。StatementHandler
:处理 SQL 语句的接口。ParameterHandler
:负责处理 SQL 参数的接口。ResultSetHandler
:处理查询结果集的接口。
method
:指定需要拦截的接口方法。常见的方法包括:update
:用于执行INSERT
、UPDATE
或DELETE
操作的Executor
接口方法。query
:用于执行SELECT
操作的Executor
接口方法。prepare
:在StatementHandler
中,用于预编译 SQL 语句。parameterize
:在ParameterHandler
中,用于设置 SQL 参数。
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
个代理,层层生成动态代理是比较耗性能的。而且虽然能指定插件拦截的位置,但这个是在执行方法时利用反射动态判断的,初始化的时候就是简单的把拦截器插入到了所有可以拦截的地方。所以尽量不要编写不必要的拦截器。另外我们可以在调用插件的地方添加判断,只要是当前拦截器 拦截的对象才进行调用,否则直接返回目标对象本身,这样可以减少反射判断的次数,提高性能。