1. 目标
使用 MyBatis 拦截器,可以实现对 Java 应用的数据库操作进行一些灵活的控制,为了便于使用,可能需要对其进行动态注册,以下进行分析
相关内容:
2. MyBatis 拦截器动态注册
在注册 MyBatis 拦截器时,可能需要使其在所有的 MyBatis 拦截器中靠前执行,也可能需要靠后执行,需要按照不同的方式实现
2.1. MyBatis 拦截器的注册与执行顺序
在 org.apache.ibatis.session.Configuration:newExecutor() 方法中,调用了 org.apache.ibatis.plugin.InterceptorChain:pluginAll() 方法,代码如下:
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
以上 interceptors 对应 InterceptorChain 类中的 List interceptors 字段
以上 pluginAll() 方法中的 “target = interceptor.plugin(target);” 处理,会使 interceptors 列表中排在后面的 MyBatis 拦截器 interceptor 先执行
InterceptorChain 类只有一个增加拦截器的方法 addInterceptor(),只能将指定的拦截器增加到 interceptors 列表最后;getInterceptors() 方法返回的拦截器列表是不可修改的
因此为了使组件中的 MyBatis 拦截器执行顺序靠后,只能使其注册顺序靠前
2.2. 使 MyBatis 拦截器靠后执行的注册方式
为了使某个 MyBatis 拦截器执行顺序靠后,需要使其注册顺序靠前
假如某个 MyBatis 拦截器通过类 A 的构造函数获取类型为 “Map<String, SqlSessionFactory> sqlSessionFactoryMap” 的参数,并在构造函数中注册了 MyBatis 拦截器 B,为了使自己的 MyBatis 拦截器执行顺序晚于以上 MyBatis 拦截器 B,则需要比以上拦截器更早注册
根据 Spring Bean 的创建顺序,会先创建被依赖的 SqlSessionFactory 类对应 Bean,再创建以上类 A 对应的 Bean
在 SqlSessionFactory 类对应 Bean 创建过程中,会调用 BeanPostProcessor 接口实现类进行 Bean 初始化前及初始化后的方法,以上方法执行都早于类 A 构造函数被调用,可以满足要求
2.2.1. MyBatis 拦截器与 SQL 会话工厂
注册 MyBatis 拦截器时,需要调用 org.apache.ibatis.session.Configuration 类的 addInterceptor() 方法
通过 org.apache.ibatis.session.SqlSessionFactory 接口的 getConfiguration() 方法可以获得对应的 Configuration 对象
在 org.mybatis.spring.SqlSessionFactoryBean 类中有字段 SqlSessionFactory sqlSessionFactory
在 SqlSessionFactoryBean 类的 afterPropertiesSet() 方法中,调用 buildSqlSessionFactory() 方法创建了 SqlSessionFactory 实现类,并赋值到 sqlSessionFactory 字段
在 SqlSessionFactoryBean 类的 getObject() 方法中,会返回 sqlSessionFactory 字段,假如为 null 会先调用 afterPropertiesSet() 创建,getObject() 方法代码如下:
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
2.2.2. 不可行方式 - 在 SqlSessionFactory 初始化前处理
在 BeanPostProcessor 实现类的 postProcessBeforeInitialization() 方法中,无法接收到 org.apache.ibatis.session.SqlSessionFactory 实现类的初始化前操作,因此不能通过这种方式注册 MyBatis 拦截器
2.2.3. 不可行方式 - 在 SqlSessionFactoryBean 初始化前处理
假如在 SqlSessionFactoryBean 初始化前,即 BeanPostProcessor.postProcessBeforeInitialization() 方法中,接收到 org.mybatis.spring.SqlSessionFactoryBean 类的初始化前操作时进行处理
通过 SqlSessionFactoryBean.getObject() 方法获得 SqlSessionFactory 实现类时,此时 SqlSessionFactoryBean 对象的 sqlSessionFactory 字段为 null,会通过 afterPropertiesSet() 方法为 sqlSessionFactory 字段赋值
以上 afterPropertiesSet() 方法定义在 org.springframework.beans.factory.InitializingBean 接口中,SqlSessionFactoryBean 类有实现该接口
由于 Bean 实现的 InitializingBean.afterPropertiesSet() 方法在 BeanPostProcessor.postProcessBeforeInitialization() 方法之后执行,SqlSessionFactoryBean 类的 afterPropertiesSet() 会再次执行,以上方法及 buildSqlSessionFactory() 方法中的操作都没有判断 sqlSessionFactory 字段是否为 null,会对 sqlSessionFactory 字段重新赋值
因此项目中实际使用的 SqlSessionFactory 实现类是 SqlSessionFactoryBean.afterPropertiesSet() 方法执行时创建的,在更早的步骤 BeanPostProcessor.postProcessBeforeInitialization() 方法中通过 SqlSessionFactoryBean.getObject() 方法获得的 SqlSessionFactory 实现类后续会被覆盖掉,在 SqlSessionFactoryBean 初始化前处理操作没有作用
2.2.4. 可行方式 - 在 SqlSessionFactory 初始化后处理
在 BeanPostProcessor 实现类的 postProcessAfterInitialization() 方法中,接收到 SqlSessionFactory 实现类的初始化后操作
调用 SqlSessionFactory.getConfiguration() 方法获得 Configuration 对象
调用 Configuration 对象的 addInterceptor() 方法增加 MyBatis 拦截器
2.2.5. 可行方式 - 在 SqlSessionFactoryBean 初始化后处理
在 BeanPostProcessor 实现类的 postProcessAfterInitialization() 方法中,接收到 SqlSessionFactoryBean 类的初始化后操作
调用 SqlSessionFactoryBean.getObject() 方法中,获得 SqlSessionFactory 对象
后续操作同上
2.2.6. 选择的实现方式
选择在 SqlSessionFactory 初始化后处理步骤,即 BeanPostProcessor 实现类的 postProcessAfterInitialization() 方法中,接收到 SqlSessionFactory 实现类的初始化后进行处理,将组件中的 MyBatis 拦截器 MyBatisConcurrencyControlInterceptor 添加到其中
2.3. 使 MyBatis 拦截器靠前执行的注册方式
为了使某个 MyBatis 拦截器执行顺序靠前,需要使其注册顺序靠后,可以处理 Spring ContextRefreshedEvent 事件时进行注册,简单示例如下:
@Service
public class TestMyBatisInterceptorRegister1 {
@EventListener
public void handleContextRefreshedEvent(ContextRefreshedEvent contextRefreshedEvent) {
TestMyBatisInterceptor testMyBatisInterceptor = new TestMyBatisInterceptor();
ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
Map<String, SqlSessionFactory> sqlSessionFactoryMap = applicationContext.getBeansOfType(SqlSessionFactory.class);
for (Map.Entry<String, SqlSessionFactory> entry : sqlSessionFactoryMap.entrySet()) {
SqlSessionFactory sqlSessionFactory = entry.getValue();
sqlSessionFactory.getConfiguration().addInterceptor(testMyBatisInterceptor);
}
}
}
TestMyBatisInterceptor 类是 MyBatis 拦截器