前言:在Spring中我们经常会使用到拦截器,在登录验证、日志记录、性能监控等场景中,通过使用拦截器允许我们在不改动业务代码的情况下,执行拦截器的方法来增强现有的逻辑。在mybatis中,同样也有这样的业务场景,有时候需要我们在不侵入原有业务代码的情况下拦截sql,执行特定的某些逻辑。那么这个过程应该怎么实现呢,同样,在mybatis中也为开发者预留了拦截器接口,通过实现自定义拦截器这一功能,可以实现我们自己的插件,允许用户在不改动mybatis的原有逻辑的条件下,实现自己的逻辑扩展。
本文将按下面的结构进行mybatis拦截器学习:
- 拦截器核心对象
- 工作流程
- 拦截器能实现什么
- 插件定义与注册
- 拦截器使用示例
- 总结
拦截器核心对象
在实现拦截器之前,我们首先看一下拦截器的拦截目标对象是什么,以及拦截器的工作流程是怎样的。mybatis拦截器可以对下面4种对象进行拦截:
1、Executor:mybatis的内部执行器,作为调度核心负责调用StatementHandler操作数据库,并把结果集通过ResultSetHandler进行自动映射;
2、StatementHandler: 封装了JDBC Statement操作,是sql语法的构建器,负责和数据库进行交互执行sql语句;
3、ParameterHandler:作为处理sql参数设置的对象,主要实现读取参数和对PreparedStatement的参数进行赋值;
4、ResultSetHandler:处理Statement执行完成后返回结果集的接口对象,mybatis通过它把ResultSet集合映射成实体对象;
工作流程
在mybatis中提供了一个Interceptor接口,通过实现该接口就能够自定义拦截器,接口中定义了3个方法:
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
- intercept:在拦截目标对象的方法时,实际执行的增强逻辑,我们一般在该方法中实现自定义逻辑;
- plugin:用于返回原生目标对象或它的代理对象,当返回的是代理对象的时候,会调用intercept方法;
- setProperties:可以用于读取配置文件中通过property标签配置的一些属性,设置一些属性变量;
看一下plugin方法中的wrap方法源码:
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
可以看到,在wrap方法中,通过使用jdk动态代理的方式,生成了目标对象的代理对象,在执行实际方法前,先执行代理对象中的逻辑,来实现的逻辑增强。以拦截Executor的query方法为例,在实际执行前会执行拦截器中的intercept方法:

在mybatis中,不同类型的拦截器按照下面的顺序执行:
Executor -> StatementHandler -> ParameterHandler -> ResultSetHandler
以执行query 方法为例对流程进行梳理,整体流程如下:
1、Executor执行query()方法,创建一个StatementHandler对象
2、StatementHandler 调用ParameterHandler对象的setParameters()方法
3、StatementHandler 调用 Statement对象的execute()方法
4、StatementHandler 调用ResultSetHandler对象的handleResultSets()方法,返回最终结果
拦截器能实现什么
在对mybatis拦截器有了初步的认识后,来看一下拦截器被普遍应用在哪些方面:
- sql 语句执行监控
可以拦截执行的sql方法,可以打印执行的sql语句、参数等信息,并且还能够记录执行的总耗时,可供后期的sql分析时使用。 - sql 分页查询
mybatis中使用的RowBounds使用的内存分页,在分页前会查询所有符合条件的数据,在数据量大的情况下性能较差。通过拦截器,可以做到在查询前修改sql语句,提前加上需要的分页参数。 - 公共字段的赋值
在数据库中通常会有createTime,updateTime等公共字段,这类字段可以通过拦截统一对参数进行的赋值,从而省去手工通过set方法赋值的繁琐过程。 - 数据权限过滤
在很多系统中,不同的用户可能拥有不同的数据访问权限,例如在多租户的系统中,要做到租户间的数据隔离,每个租户只能访问到自己的数据,通过拦截器改写sql语句及参数,能够实现对数据的自动过滤。
除此之外,拦截器通过对上述的4个阶段的介入,结合我们的实际业务场景,还能够实现很多其他功能。
插件定义与注册
在我们自定义的拦截器类实现了Interceptor接口后,还需要在类上添加**@Intercepts** 注解,标识该类是一个拦截器类。注解中的内容是一个**@Signature对象的数组,指明自定义拦截器要拦截哪一个类型的哪一个具体方法。其中type指明拦截对象的类型,method是拦截的方法,args是method执行的参数。通过这里可以了解到 mybatis 拦截器的作用目标是在方法级别上进行拦截,例如要拦截Executor的query**方法,就在类上添加:
@Intercepts({
@Signature(type = Executor.class,method = "query", args = {
MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class })
})
如果要拦截多个方法,可以继续以数组的形式往后追加。这里通过添加参数可以确定唯一的拦截方法,例如在Executor中存在两个query方法,通过上面的参数可以确定要拦截的是下面的第2个方法:
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql);
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler);
当编写完成我们自己的插件后,需要向mybatis中注册插件,有两种方式可以使用,第一种直接在SqlSessionFactory中配置:
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setPlugins(new Interceptor[]{
new ExecutorPlugin()});
return sqlSessionFactoryBean.getObject();
}
第2种是在mybatis-config.xml中对自定义插件进行注册:
<configuration>
<plugins>
<plugin interceptor="com.cn.plugin.interceptor.MyPlugin">
<property name="text" value="hello"/>
</plugin>
<plugin interceptor="com.cn.plugin.interceptor.MyPlugin2"></plugin>
<plugin

最低0.47元/天 解锁文章
3765

被折叠的 条评论
为什么被折叠?



