文章目录
- 一、开篇
- 二、源码分析
- 1.selectList()
- 2.CachingExecutor
- 3、createCacheKey
- 1)为什么CachingExecutor要调用SimpleExecutor的方法去创建缓存key呢?
- 4、创建好缓存key之后:query查询二级缓存
- 5、如果二级缓存为空继续走查询query查询一级缓存
- 6、查询一级缓存为空queryFromDatabase查询数据库
- 三、演示
- 四、总结
一、开篇
在我们分析Executor的时候最后一步就是查询我们的数据库 本篇就是去深究一下Mybatis怎样帮我们封装查询数据库的呢?
二、源码分析
1.selectList()
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 拿到封装好的Mapper对应关系以及SQL语句
MappedStatement ms = configuration.getMappedStatement(statement);
//执行query去查询数据
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
我们看到在SelectList中第一步帮我们拿到了封装的SQL语句然后去执行query方法
2.CachingExecutor
我们看源码的时候他又两个实现类,那么会走哪个以及为什么走CachingExecutor这个呢?我们在分析openSession的时候就分析郭了,Executor初始话的时候会先创建简单的然后最后创建并且返回的是CachingExecutor所以我们可以断定这步执行的是CachingExecutor;废话少说直接看代码
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);//拿到SQL语句
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);//创建缓存key
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
3、createCacheKey
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}
这里我们可以去看到delegate.createCacheKey,是调用了简单执行器的方法去执行的,自己的方法调用别人的那么问题来了
1)为什么CachingExecutor要调用SimpleExecutor的方法去创建缓存key呢?
Mybatis缓存时先查二级缓存,再查以及缓存。当二级缓存中没有的话才去查一级缓存
二级缓存和一级缓存都需要创建缓存key这样写方便代码重构和减少冗余所以我们可以理解为二级缓存实质上是一个空壳
4、创建好缓存key之后:query查询二级缓存
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) { //如果二级缓存不为空的话直接返回二级缓存中的list
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//如果二级缓存为空
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
5、如果二级缓存为空继续走查询query查询一级缓存
@Override
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();
}
//queryStack 为了保证线程安全 再执行clearLocalCache操作的时候没有线程对再去queryStack++
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;//查询一级缓存
if (list != null) {一级缓存不为空就返回一级缓存的内容
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {//一级缓存为空走一级缓存
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
queryStack为了保证线程安全,但是这里有个问题queryStack没有用原子类所以再并发条件先会出现问题的
protected int queryStack = 0;
6、查询一级缓存为空queryFromDatabase查询数据库
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);//EXECUTION_PLACEHOLDER占位符
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);//查询结果
} finally {
localCache.removeObject(key);/清除掉之前的带占位符的key
}
localCache.putObject(key, list);//添加结果到一级缓存中去
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;/返回结果集
}
三、演示
public class Test {
public static void main(String[] args) {
try {
//1、定义Mybatis文件
String resource = "mybatis_config.xml";
//2、IO操作 使用java的api读取配置文件获取InputStreamReader
Reader resourceAsReader = Resources.getResourceAsReader(resource);
//3、获取SQLSessionFactoryBuilder
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsReader);
//4、通过获取SQLSessionFactoryBuilder获取session
SqlSession sqlSession = sqlSessionFactory.openSession();
//5、通过SqlSession去操作Mapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//6、执行SQL语句
System.out.println("------------------------第一次调用-------------------------------");
List<User> users = mapper.selectAllUser();
for (User user : users) {
System.out.println(user.toString());
}
System.out.println("------------------------第二次调用-------------------------------");
List<User> users1 = mapper.selectAllUser();
for (User u : users1) {
System.out.println(u.toString());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
我们打印结果
==> Preparing: select * from user
==> Parameters:
<== Columns: id, username, password
<== Row: 1, 小王, 123456
<== Row: 2, 小李, 123456
<== Total: 2
User{id=1, username='小王', password='123456'}
User{id=2, username='小李', password='123456'}
------------------------第二次调用-------------------------------
User{id=1, username='小王', password='123456'}
User{id=2, username='小李', password='123456'}
可以看到第一次执行SQL语句第二次直接从一级缓存中取出来了
此时中间我们去手动加一行代码( sqlSession.clearCache();)再两次执行中间手动清除一级缓存看看结果
==> Preparing: select * from user
==> Parameters:
<== Columns: id, username, password
<== Row: 1, 小王, 123456
<== Row: 2, 小李, 123456
<== Total: 2
User{id=1, username='小王', password='123456'}
User{id=2, username='小李', password='123456'}
------------------------第二次调用-------------------------------
==> Preparing: select * from user
==> Parameters:
<== Columns: id, username, password
<== Row: 1, 小王, 123456
<== Row: 2, 小李, 123456
<== Total: 2
User{id=1, username='小王', password='123456'}
User{id=2, username='小李', password='123456'}
此时可以看到我们两次都打印SQL语句,这是因为我们在执行完第一条SQL语句把我们的一级缓存清楚了
四、总结
通过以上分析,我们可以看到一级缓存是Mybatis中自带的,二级缓存其实就是一个空壳(在不配置的情况下)。