在阅读mybatis源码时,发现问题:
源码org.apache.ibatis.executor.BaseExecutor#query,如下:
protected int queryStack;
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//处理Callable类型,还需要绑定IN/OUT参数
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//本地缓存没有结果,直接查询数据库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
// ... codeA ...
}
return list;
}
我无法明白这个queryStack变量的意义。
通过控制流程来看:
- 假如这段代码允许多线程并发,那么int变量一定会出现线程安全问题。
- 假如这段代码仅允许单线程跑,那么只可能是因为存在递归的情况,才需要引用int来记录stack,但是我跟代码半天,也没有发现哪里会递归。
有没有大佬讲讲啥时候这个方法会递归调用?或者是其他作用。
首先 BaseExecutor 就没有设计为允许多线程并发调用,因为你看它的代码就知道一个 BaseExecutor 对象包含一个 Transaction,这样的设计表示它专属于某个数据库事务,而数据库事务的执行就是单线程的。换句话说,用户在调用 SqlSessionFactory.openSession() 方法,得到 SqlSession 对象之后,都不可以使其被多个线程访问。
其次说到 queryStack 这个变量。确实框架本身并没有直接的递归调用,但框架设计了一个扩展机制,你可以在 InterceptorChain 里面看到,框架允许用户创建自己的 Interceptor 实现类。这个实现类将会在 Configuration.newStatementHandler() 方法中被调用,而这个方法会在 BaseExecutor 的多个子类中用到。举一个例子,调用链如下:
调用到了这里,用户创建的 Interceptor 就开始起作用了,框架无法预料用户会怎么写,如果用户在这个过程中又调用了 Executor.query() 方法,这就可能会制造出一个递归。