可拦截的接口
可以拦截的Mybatis四大核心组件
-
ExecutorMyBatis 执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
-
StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合
-
ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数
-
ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
拦截器加载与执行
- springIOC初始化SqlSessionFactoryBean的时候执行afterPropertiesSet()方法去构建SqlSessionFactory,这个过程会加载所有的拦截器到一个List中
public void afterPropertiesSet() throws Exception {
this.sqlSessionFactory = buildSqlSessionFactory();
}
//读取配置的拦截器
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
//省略...
//添加所有拦截器
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
//省略...
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
// InterceptorChain,持有所有拦截器
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
- 在实例化Executor、ParameterHandler、ResultSetHandler、StatementHandler四大接口对象的时候分别调用interceptorChain.pluginAll()方法。基于责任链模式循环执行拦截器链所有的拦截器的plugin() 方法。
Executor为例:
// 构造Executor对象
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//省略...
//调用pluginAll方法,executor在这里已经被修改成一个代理对象。
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
拦截器使用
主键赋值插件
mybatis常用于分页查询,比如开源的分页插件PageHelper就是基于拦截器实现,动态修改sql语句实现分页。本人项目中写过一个insert主键自动赋值拦截器,示例只是简单演示原理。
实现mybatis提供接口Interceptor实现它的三个方法:
-
plugin 方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法,这就是intercept的拦截原理
-
setProperties 方法是用于在Mybatis配置文件中指定一些属性的。
-
intercept 拦截目标对象的方法执行
//Signature签名注解
public @interface Signature {
Class<?> type(); //拦截类型
String method(); //拦截的方法
Class<?>[] args(); //拦截的参数
}
@Intercepts(@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }))
public class GenerateKeyInterceptor implements Interceptor {
// 自定义属性
private String idType;
/**
* 拦截目标对象的方法执行
*
* @param invocation
* @return
* @throws Throwable
*/
public Object intercept(Invocation invocation) throws Throwable {
//获取xml中的一个select|update|insert|delete节点
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object paramObj = invocation.getArgs()[1]; // 获取参数
if (SqlCommandType.INSERT.equals(mappedStatement.getSqlCommandType())) { // 获取 SQL类型
// MetaObject是mybatis提供的反射工具类
MetaObject metaObject = SystemMetaObject.forObject(paramObj);
if (metaObject.getValue("id") == null && "String".equals(metaObject.getSetterType("id").getSimpleName()) {
metaObject.setValue("id", getUUID());
}
}
// 执行目标方法
return invocation.proceed();
}
/**
* 为目标对象创建代理对象
*
* @param target
* @return
*/
public Object plugin(Object target) {
//利用mybatis工具类创建代理对象
if (target instanceof Executor) {
return Plugin.wrap(target, this);
}
return target;
}
/**
* 自定义配置信息
*
* @param properties
*/
public void setProperties(Properties properties) {
this.idType = properties.getProperty("idType");
}
private String getUUID() {
return UUID.randomUUID().toString().replace("-", "");
}
}
配置插件
@Configuration
@MapperScan("com.**.mapper")
public class MybatisConfig {
//省略...
// SqlSessionFactoryBean
@Bean
public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
//数据源
sqlSessionFactoryBean.setDataSource(dataSource());
// mapper文件路径
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
// 别名,让*Mpper.xml实体类映射可以不加上具体包名
sqlSessionFactoryBean.setTypeAliasesPackage("com.**.entity");
//自定义插件
sqlSessionFactoryBean.setPlugins(generateKeyInterceptor());
//驼峰命名
sqlSessionFactoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
return sqlSessionFactoryBean;
}
/**
* 自定义全局主键拦截器
*
* @return
*/
@Bean
public GenerateKeyInterceptor generateKeyInterceptor() {
GenerateKeyInterceptor generateKeyPlugin = new GenerateKeyInterceptor();
Properties properties = new Properties();
properties.put("idType", "自定义属性");
generateKeyPlugin.setProperties(properties);
return generateKeyPlugin;
}
}
执行顺序
-
不同类型 Executor→StatementHandler→ParameterHandler→ResultSetHandler
-
同类型,配置在前的后执行,即最后的先执行,原因是拦截器通过了层层代理嵌套执行,最外层先执行