Mybatis是当今最普遍使用的数据持久化框架,这点拒绝反驳。
Mybatis调用链:
SqlSessionInterceptor.invoke()
DefaultSqlSessionFactory.openSession(executorType): DefaultSqlSession
->Configuration.newExecutor(): sqlSession //interceptorChain.pluginAll(excutor);
DefaultSqlSession.selectList(String statement, Object parameter, RowBounds rowBounds)
->CachingExecutor.query(); =>SimpleExecutor.query() //queryFromDatabase
->Configuration.newStatementHandler(): StatementHandler
->new RoutingStatementHandler();
->configuration.newParameterHandler() //interceptorChain.pluginAll(parameterHandler);
->configuration.newResultSetHandler(); //interceptorChain.pluginAll(resultSetHandler);
->interceptorChain.pluginAll(statementHandler); //*****注册插件(共四类)、生成代理类
->Plugin.wrap(target, this); ->Proxy.newProxyInstance(cl, interfaces, new Plugin(target, interceptor, signatureMap));
->RoutingStatementHandler.prepare(connection, timeout);
->RoutingStatementHandler.parameterize(stmt)
可见,mybatis可以再四个地方使用插件:Executor、ParameterHandler、ResultHandler、StatementHandler。大部分情况下我们都会实现Executor的插件,比如分页、改变sql参数等。自定义一个插件主要三步:
1、继承 org.apache.ibatis.plugin.Interceptor,并实现方法 Object intercept(Invocation invocation),mybatis_3.5之前还要实现Object plugin(Object target) ;
2、声明拦截的类型,如:@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})}
3、加入spring容器中,特别要注意它处于InterceptorChain链中的位置,这样会直接影响执行效果,因为mybatis的拦截器执行顺序与声明的位置相反。
最后特别要说明一个网上流行的分页组件 github.PageHelper,这哥们做的特别恶心,将自己放在最后一个,即第一个执行,并且硬性改变了executor的执行方法,使得后面拦截器可能会无法执行,具体看他的实现:
@Override
public Object intercept(Invocation invocation) throws Throwable {
try {
.....
if (args.length == 4) {
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
checkDialectExists();
List resultList;
if (!dialect.skip(ms, parameter, rowBounds)) {
if (dialect.beforeCount(ms, parameter, rowBounds)) {
Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
if (!dialect.afterCount(count, parameter, rowBounds)) {
return dialect.afterPage(new ArrayList(), parameter, rowBounds);
}
}
resultList = ExecutorUtil.pageQuery(dialect, executor,
ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
} else {
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
....
}
}
从这里看出,它破坏了chain的执行顺序,并没有调用 invocation.proceed(),而是直接调用了留个参数的query方法。
说完了。