SqlSessionFactory
SqlSessionFactory:主要作用是创建SqlSession。SqlSessionFactory的生命周期随着整个Mybatis框架的运行而运行,随着Mybatis框架的销毁而销毁。
SqlSession
SqlSession:主要是用来执行sql。
一级缓存
mybatis是默认开启一级缓存的。而一级缓存的作用域是SqlSession。接下来使用mybatis的查询来验证一下一级缓存。
整体代码:
//加载mybatis配置文件
String source = "mybatis/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(source);
//创建SqlSessionFactory工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
//利用工厂对象创建session会话
SqlSession session1 = factory.openSession();
SqlSession session2 = factory.openSession();
//第一个mapper
LocalMapper localMapper = session1.getMapper(LocalMapper.class);
TbLocal local = localMapper.cc(); // 查询出来的对象
session1.commit();
//第二个mapper
LocalMapper localMapper1 = session2.getMapper(LocalMapper.class);
TbLocal tbLocal = localMapper1.cc(); // 查询出来的对象
- 查询逻辑分析(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) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 具体执行一级缓存的逻辑
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
2.往一级缓存中 (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();
}
List<E> list;
try {
queryStack++;
// 从一级缓存中获取
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 如果不为空,执行的操作。然后直接返回list
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;
}
咱们仔细看这一段代码。
#从一级缓存中获取对象
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
进入localCache.getObject()方法中。
我们看到 localCache的类型是PerpetualCache,其中cache为一个map,暂时猜测一级缓存就在这个map中。那么可以得出,在sqlsession查询时,会将缓存put到PerpetualCache中的cache中。
而我们知道,一级缓存作用域是SqlSeesion,那我就可以猜测PerpetualCache对象应该是在创建SqlSeesion时创建的。
接下来进入openSession的源码分析(也就是创建SqlSession的执行逻辑),看看有没有创建PerpetualCache
//创建SqlSessionFactory工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
//利用工厂对象创建session会话
SqlSession session1 = factory.openSession();
3.openSession的执行过程分析
进入openSession一路调试进入到如下方法中,并且进入newExecutor方法中(前面的一些配置信息,暂时忽略,咱们只看和缓存相关即可)。
再进入newExecutor方法中。
在该方法中创建了一些和Executor相关的对象。调试后发现只能进入,new SimpleExcutor 和 new CachingExecutor中(下图)。咱们先看SimpleExcutor。咱们根据下图片右边圈出来的类继承关系图,就能知道。不管创建哪个Excutor对象,肯定会先创建父类的BaseExecutor对象。
咱再看看顶层的 BaseExecutor 都有哪些属性。
从这里可以看出,BaseExecutor中包含了PerpetualCache。也就是上文所解释的一级缓存主要的存储对象。
我们再看看new CachingExecutor(executor);对象。我们可以看到,CachingExecutor中将delegate设置了值,而delegate是Excutor类型。BaseExecutor继承Excutor。所以delegate属性中有BaseExecutor对象(也就是一级缓存)。
那么由此得出,在创建sqlsession时,会先创建PerpetualCache对象,在进行查询时,会将缓存put入PerpetualCache的cacha中。那么也就可以解释为什么一级缓存的作用域是sqlSeesion了。每个SqlSeesion的创建都会创建一个PerpetualCache,一个sqlsession对应一个PerpetualCache。
二级缓存
二级缓存作用域是SqlSessionFactory。随着SqlSessionFactory创建而创建,销毁而销毁。继续进入查询方法如下。也就是上文中的查询(query)方法:
// 获取二级缓存
Cache cache = ms.getCache();
if (cache != null) {
// 二级缓存不为空
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 从tcm中获取(该tcm实际上并不是二级缓存,而是暂存区(后续commit方法中会讲解)。咱们接下来进入tcm中查看)
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
进入tcm.getObject()方法中
发现tcm是一个TransactionalCacheManager类型的对象。其中有个属性transactionalCaches(value值),key为Cache,value为TransactionalCache。tcm.getObject方法中的getTransactionalCache方法实际上就是创建了一个TransactionalCache对象,然后再调用TransactionalCache类中的getObject方法,那么进入TransactionalCache中的getObject方法中。如下:
1.Object object = delegate.getObject(key);
delegate可以看到类型为Cache。使用过mybatis二级缓存的都知道。我们可以自己实现一个Cache接口,去实现自定义缓存。而这里的getObject方法就可以进入到我们自定义二级缓存中的实现中去。这里的意思是从二级缓存中获取数据。第一次查询肯定为空。
2. entriesMissedInCache.add(key);
如果为空则 将key添加到entriesMissedInCache中,key为namespace+sql+一些序列化的东西。标识唯一。然后返回,回到query方法
我们再进入到query方法中:
代码解释如下:
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 如果二级缓存不存在该数据,则从数据库中查询
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 再将查询到的数据添加到暂存区中
这里主要是添加到TransactionalCacheManager对象(暂存区)中的map中去
tcm.putObject(cache, key, list); // issue #578 and #116
}
由此我们总结出,mybatis的二级缓存在查询时,并不会立刻将数据存入Chache(二级缓存)中。那我们可以思考一下,在什么时候才能将数据存入Cache(二级缓存)中呢。为了解决疑惑,咱们继续进入commit()方法中查看.
commit执行分析
这里主要两步操作。
1.delegate.commit 清空一级缓存(这里为什么要清空一级缓存呢?)
首先我们知道,一级缓存作用域是sqlsession。在同一个事务的情况下,mybatis执行的 所有sql都是同一个sqlsession对象进行操作的。除非创建了一个新的事务。spring中使用@Transactional(propagation = Propagation.REQUIRES_NEW)来创建一个新的事务。那么此时,在新事务中会new一个sqlsession来执行sql。那么如果我们在同一个事务中,执行多条sql时都是同一个sqlsession。只有当sql执行完了之后,并且无异常,commit后,就将一级缓存清除。注意:如果在执行sql出现异常并调用session.rollback时,也会清除缓存。
进入delegate.commit方法中,最终会进入如下方法:
@Override
public void clearLocalCache() {
if (!closed) {
// 这里的localCache就是一级缓存(PerpetualCache对象)
localCache.clear();
localOutputParameterCache.clear();
}
}
2.tcm.commit 清除二级缓存。
进入tcm.commit中
public void commit() {
// 遍历暂存区中的数据
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
进入 txCache.commit();
public void commit() {
#clearOnCommit :true表示执行了更新,修改,添加操作。false表示执行了查询操作。
if (clearOnCommit) {
// 如果没有执行查询操作,则将二级缓存清空(这里如果实现列自定义的二级缓存,会进入自定义实现类中)
delegate:的类型是Cache(二级缓存)。
delegate.clear();
}
// 将暂存区的数据添加到二级缓存中。
flushPendingEntries();
reset();
}
进入 flushPendingEntries();
private void flushPendingEntries() {
// 遍历拿到暂存区中的数据,一个一个添加到二级缓存中
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
那么flushPendingEntries方法具体执行逻辑为: 遍历所有的暂存区的数据,添加到缓存中。
因此证实了我的猜想,二级缓存逻辑。查询会将数据存入暂存区中,只有等到sql执行完成并没有异常。commit之后。才会将数据存入二级缓存中。并且commit时还会判断是不是修改操作。如果是的话,会清空二级缓存。那么我们继续看修改操作。
修改执行过程
修改源码跟踪
进入flushCacheIfRequired(ms);如下:
private void flushCacheIfRequired(MappedStatement ms) {
// 获取二级缓存中的数据。
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
// 如果二级缓存不为空,清空暂存区的数据
tcm.clear(cache);
}
}
进入 tcm.clear(cache);如下:
这里看到,是进入了暂存区中的clear方法。因此这里执行的是清空暂存区的数据。咱们继续往下看。
@Override
public void clear() {
// 这个属性是之前commit中的那个判断。修改就设置为true
clearOnCommit = true;
// 清空暂存区的数据
entriesToAddOnCommit.clear();
}
回到上面我说的修改源码开头。如下
进入update方法:
然后直接进入 clearLocalCache 方法.如下:
@Override
public void clearLocalCache() {
if (!closed) {
// 这里的localCache是PerpetualCache对象。
localCache.clear();
localOutputParameterCache.clear();
}
}
在文章开头源码的解释已经了解到。PerpetualCache 是一级缓存对象。因此这里主要的逻辑是清空一级缓存。
最后让我们继续回顾一下修改的方法:
flushCacheIfRequired:主要的功能是清空二级缓存
delegate.update(ms, parameterObject): 主要的逻辑是清空一级缓存。
因此我们结合之前查询,commit,修改来说:不管是查询还是修改,在二级缓存情况下。
查询:只会将数据存入暂存区。
修改: 只会将暂存区的数据清除。
commit:
1.判断是否是修改操作。修改就清空二级缓存
2.不是修改。就遍历暂存区的数据,然后一个一个添加到二级缓存中。flushPendingEntr ies方法在上面的commit中。
版权声明:内容来源于互联网和用户投稿 如有侵权请联系删除