Mybatis的一二级缓存原理解析(请使用mybatis跟着走)

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(); // 查询出来的对象

  1. 查询逻辑分析query

@Override

public List 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 list = (List) 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 List 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 list;

try {

queryStack++;

// 从一级缓存中获取

list = resultHandler == null ? (List) 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) 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 list = (List) 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 list = (List) 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 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中。

展开阅读全文

页面更新:2024-04-25

标签:缓存   遍历   二级缓存   逻辑   原理   对象   作用   事务   操作   方法   数据

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top