mybatis两级缓存原理剖析
https://blog.csdn.net/zhurhyme/article/details/81064108
对于mybatis的缓存认识一直有一个误区,所以今天写一篇文章帮自己订正一下。mybatis的缓存分一级缓存与二级缓存。下面分别对这两种缓存进行详细解说。
mybatis 的调用链
首先我们需要大致的了解一下mybatis的调用链,然后才能更好的理解mybatis的缓存。
主要的api罗列如下:
public interface SqlSessionFactory {
SqlSession openSession();
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);//重点是这个方法
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
Configuration中的
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {// 二级缓存开关
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor); //mybatis的插件机制
return executor;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
上面的代码大致描述了,我们为了获取一个sqlSession实例的过程。下面的方法为SqlSession的实现类DefaultSqlSession中的一个查询方法.
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
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();
}
}
1
2
3
4
5
6
7
8
9
10
11
从该方法我们可知SqlSession的方法实现全部委托给了Executor实例。Executor接口的类图:
这里写图片描述
mybatis 一级缓存
上面的executor.query如何执行呢?public abstract class BaseExecutor 中的方法给了我们答案.
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
@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) {
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
请注意localCache是什么呢?在BaseExecutor的构造器有这么一句代码: this.localCache = new PerpetualCache(“LocalCache”); 即locaCache为缓存,即mybatis的一级缓存。
localCache生命周期是与SqlSession的生命周期是一样;不同的sqlSession会生成不同的localCache.这就是mybatis的一级缓存,它是与sqlSession息息相关,只能单个sqlSession实例独享。并且默认是开启的,没有开关可以关闭它。但是它的使用非常有局限。所以mybatis才需要在二级缓存,即突破SqlSession范围的缓存。mybatis的二级缓存与我们通常认知的缓存没有区别。
mybatis 的二级缓存
mybatis的二级缓存即通过CachingExecutor来实现。
CachingExecutor 中的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.<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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
即当cache不为null时,则从tcm中获取,如果获取不到则再从delegate中获取。
现在关键是tcm.getObject(cache, key);
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
TransactionalCacheManager的部分源代码
public class TransactionalCacheManager {
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
private TransactionalCache getTransactionalCache(Cache cache) {
/*
关键代码,它的作用从transactionalCaches 中获取TransactionalCache ,如果没有,则将cache作为参数,重新new一个,这段代码涉及jdk8的新语法及以及新特性,
不太容易懂,如果有小朋友看不懂,我建议你好好学习一下jdk8
*/
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
TransactionalCache 部分源码
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<>();
this.entriesMissedInCache = new HashSet<>();
}
@Override
public Object getObject(Object key) {
// issue #116
Object object = delegate.getObject(key);
if (object == null) {
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
峰回路转
Cache cache = ms.getCache(); //这句是重点
1
即只要MappedStatement ms 自己有cache,则直接使用,这个就是mybatis的二级缓存。关于开启mybatis的二级缓存,则需要在config中配置cacheEnabled 为ture,它的默认值即为true。
另外需要在mapper中配置
<cache />
1
这样就可以最简单的方式使用mybatis的二级缓存了。至于如何扩展mybatis的二级缓存,不在本文之列。
mybatis 二级缓存絮叨
至此mybatis 的二级缓存好像已经说明白了。但是还想在此絮叨一下,原因是为了更好的理解什么是缓存。mybatis的二级缓存cache它是跟在MappedStatement上的。我们设置 cache标签,是以mapper为单位的。即这个mapper文件内的statement 共用这个cache标签。cache它与sqlSession没有任何关系,即不同的sqlSession执行同一个statement就可以共用同一个cache实例了。
那么是否有更大的cache使用范围呢?答案是有的,举个栗子。 部门表被cache;人员表被cache,从上面我们可知,这是两个不同的cache实例。在人员mapper中如何出人员与部门的联合查询,则当部门更新时,会出现局部的数据不一致。如何解决这个问题呢?一种答案是去掉缓存;另一种就是mybatis给我们提供的
<cache-ref >
1
标签,它可以使多个mapper使用同一个cache实例,从而达到缓存数据的一致性(关于缓存数据的一致性,性能,缓存数据范围,在此不讨论)。
---------------------
作者:zhurhyme
来源:CSDN
原文:https://blog.csdn.net/zhurhyme/article/details/81064108
版权声明:本文为博主原创文章,转载请附上博文链接!
mybatis两级缓存原理剖析的更多相关文章
- 用guava快速打造两级缓存能力
首先,咱们都有一共识,即可以使用缓存来提升系统的访问速度! 现如今,分布式缓存这么强大,所以,大部分时候,我们可能都不会去关注本地缓存了! 而在一起高并发的场景,如果我们一味使用nosql式的缓存,如 ...
- Spring+ehcache+redis两级缓存
问题描述 场景:我们的应用系统是分布式集群的,可横向扩展的.应用中某个接口操作满足以下一个或多个条件: 1. 接口运行复杂代价大, 2. 接口返回数据量大, 3. 接口的数据基本不会更改, 4. 接口 ...
- Redis+Caffeine两级缓存,让访问速度纵享丝滑
原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 在高性能的服务架构设计中,缓存是一个不可或缺的环节.在实际的项目中,我们通常会将一些热点数据存储到Redis或MemCache这类缓存中间件中, ...
- 基于Spring接口,集成Caffeine+Redis两级缓存
原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 在上一篇文章Redis+Caffeine两级缓存,让访问速度纵享丝滑中,我们介绍了3种整合Caffeine和Redis作为两级缓存使用的方法,虽 ...
- MyBatis:二级缓存原理分析
MyBatis从入门到放弃七:二级缓存原理分析 前言 说起mybatis的一级缓存和二级缓存我特意问了几个身边的朋友他们平时会不会用,结果没有一个人平时业务场景中用. 好吧,那我暂且用来学习源码吧.一 ...
- J2CACHE 两级缓存框架
概述 缓存框架我们有ehcache 和 redis 分别是 本地内存缓存和 分布式缓存框架.在实际情况下如果单台机器 使用ehcache 就可以满足需求了,速度快效率高,有些数据如果需要多台机器共享这 ...
- springboot中使用自定义两级缓存
工作中用到了springboot的缓存,使用起来挺方便的,直接引入redis或者ehcache这些缓存依赖包和相关缓存的starter依赖包,然后在启动类中加入@EnableCaching注解,然后在 ...
- (4)一起来看下mybatis框架的缓存原理吧
本文是作者原创,版权归作者所有.若要转载,请注明出处.本文只贴我觉得比较重要的源码,其他不重要非关键的就不贴了 我们知道.使用缓存可以更快的获取数据,避免频繁直接查询数据库,节省资源. MyBatis ...
- MyBatis 源码分析 - 缓存原理
1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...
随机推荐
- Object.create 以及 Object.setPrototypeOf
第一部分 Object.crate() 方法是es5中的关于原型的方法, 这个方法会使用指定的原型对象以及属性去创建一个新的对象. 语法 Object.create(proto, [ properti ...
- 虚拟机复制操作CentOS6导致eth0转为eth0以至于网络服务启动失败的解决方案
CentOS6复制到虚拟机中eth0转为了eth1 原因: CentOS6硬盘上的信息保存着操作系统的信息, 将该硬盘上的信息拷贝到另一开新的硬盘上时, 硬件的环境就会一定会发生变化, 就那网卡来首, ...
- HDU 4334——Trouble——————【贪心&水题】
Trouble Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Su ...
- HBuilder更换部分
1.HBuilder工作空间的更换 HBuilder的默认工作空间的修改并不像其他ide一样,在设置里进行更改,而是在工具中进行设置. 1.单击菜单栏“工具”,选择“变更默认代码存放目录” 2.进行修 ...
- 对fastdfs 文件清单进行检查,打印无效的文件
对fastdfs 文件清单进行检查,打印无效的文件2017年12月12日 18:37:18 守望dfdfdf 阅读数:281 标签: fastdfssftpmysql 更多个人分类: 工作 问题编辑版 ...
- jsp---tomcat===》》内置对象
1.内置对象: request: 方法:getParameter("txtName"):获取和页面上的name属性对象的value值 返回String ...
- 安全漏洞 : XSS CSRF
https://my.oschina.net/hc24/blog/527099 XSS成因概括 : XSS其实就是Html的注入问题,攻击者A的输入没有经过严格的控制进入了数据库,最终显示给来访的用户 ...
- vue多个组件的过渡
<transition name="component-fade" mode="out-in"> <component v-bind:is=& ...
- Chrome浏览器安装vue-devtools插件
插件功能:方便在浏览器调试vue代码 插件git地址:https://github.com/vuejs/vue-devtools 因为chrome要FQ,打不开,所以不能直接进去安装拓展程序,只能选择 ...
- 【起航计划 029】2015 起航计划 Android APIDemo的魔鬼步伐 28 App->Preferences->Default Values 偏好默认值
DefaultValues 介绍了如何在XML中定义Preference的缺省值. <CheckBoxPreference android:key="default_checkbox& ...