1. Mybatis能拦截的接口
myBatis可以对四大接口进行拦截:Executor, StatementHandler, ResultSetHandler, ParameterHandler
myBatis执行流程
- Executor 是mybatis的内部执行器,它通过调用StatementHandler来操作数据库
- StatementHandler是Mybatis直接和数据库执行sql脚本的对象。
- ParameterHandler是Mybatis实现Sql入参设置的对象。插件可以改变我们Sql的参数默认设置。
- ResultSetHandler是Mybatis把ResultSet集合映射成POJO的接口对象。我们可以定义插件对Mybatis的结果集自动映射进行修改。
上面四个接口下的方法,就是mybatis插件允许拦截的方法。
2. Interceptor 接口
mybatis插件,必须要实现Interceptor接口;
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
- plugin: 用来封装目标对象。可以返回目标对象本身,也可以根据实际需要,创建一个代理对象,进而实现自定义功能; 注意,不要随意创建plugin,只为需要拦截的接口创建plugin
public Object plugin(Object target) {
if (target instanceof StatementHandler || target instanceof ResultSetHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
- intercept: 进行拦截时,要执行的方法
- setProperties方法是在Mybatis进行配置插件的时候可以配置自定义相关属性,即:接口实现对象的参数配置
3. 插件生成原理
首先我们来看看,mybatis如何生成四个接口对象
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//确保ExecutorType不为空(defaultExecutorType有可能为空)
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor; if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
} if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
可以看到,四个接口对象在生成时,都会调用InterceptorChain.pluginAll()
方法。这个方法会调用pluginChain,执行所有的plugin,生成对应的接口对象。所以,在实现plugin方法时判断一下目标类型,是本插件要拦截的对象才执行Plugin.wrap方法,否者直接返回目标本省,这样可以减少目标被代理的次数。
4. 配置注解@Intercepts
mybatis通过@Intercepts来配置拦截哪个对象的哪个拦截方法
@Intercepts({@Signature(
type = Executor.class, //拦截的类
method = "query", //拦截类的某个方法
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})// 对应方法的参数
public class TestInterceptor implements Interceptor {
}
5. spring项目中使用插件
在创建SqlSessionFactory时,插入Interceptor List
@Bean
public Interceptor myFirstPlugin() {
return new MyFirstPlugin();
}
@Bean
public Interceptor mySecondPlugin() {
return new MySecondPlugin();
}
@Bean
public SqlSessionFactoryBean sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
Interceptor[] plugins = new Interceptor[]{mySecondPlugin(), myFirstPlugin()};
sqlSessionFactoryBean.setPlugins(plugins);
return sqlSessionFactoryBean;
}
网友评论