DefaultSqlSession 真正的执行代码是由 BaseExecutor 进行执行的,而BaseExecutor中的代码如下:
定义一个localCache字段,其实底层就是一个HashMap。
protected PerpetualCache localCache;
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
// .....
BaseExecutor 用这个HashMap缓存了 SqlSession的 select/selectList的查询结果。
所以这个可以理解成一个共享竞争资源
我们看一下BaseExecutor的查询数据库代码
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
// ①
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
当不同的线程使用sqlsession进行查询的时候,会出现以下的情况
- 线程1 使用select * from user 进行查询,查询出5条数据,
- 线程2同样使用语句,并且这个时候是线程1刚好执行到①处,则还没有放入缓存,这个时候直接查库,但是线程2查询的时候,数据库刚好新插入了1条,所以查询到的是6条,
- 线程2把查询到的6条放入缓存
- 然后线程1又把第5条放入了缓存,
- 这样就导致了后续的查询都使用的是线程1的缓存,导致查询结果不正确。
附一个线程安全的SqlSession
实现思路就是创建一个包装类,每一次真正执行的时候,使用的是当前绑定线程的SqlSession
package com.wuhulala.springboot.mybatis.threadsafe;
import org.apache.ibatis.cursor.Cursor