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 自然 ...
随机推荐
- 百度网页分享js代码
1.小图标 <div class="bdsharebuttonbox"> <a href="#" class="bds_qzone& ...
- Json的访问
JSON:JavaScript 对象表示法(JavaScript Object Notation) 写法:名称/值对 访问方法:可以通过 data.名称 访问,也可以通过 data['名称'] 访问 ...
- ASP.NET那点不为人知的事(一)
http://www.cnblogs.com/OceanEyes/archive/2012/08/13/aspnetEssential-1.html#_label0 我们上网时,在浏览器地址输入网址: ...
- jQuery Validate使用说明
jQuery Validate 导入 js 库 <script src="./jquery-validation/lib/jquery-1.8.3.js" type=&quo ...
- iDempiere 使用指南 插件安装过程
Created by 蓝色布鲁斯,QQ32876341,blog http://www.cnblogs.com/zzyan/ iDempiere官方中文wiki主页 http://wiki.idemp ...
- Java中mouseDragged有效mouseMoved没响应的可能原因
1.这个问题在jdk7与jdk8上都会出现. 2.具体表现为: 单独写个测试例子,用JFrame实现了mouseMoved接口,mouseDragged和mouseMoved都输出方法名和坐标,结果是 ...
- sharepoint2010列表的分页实现迅雷样式效果
利用ListItemCollectionPosition和AspNetPage分页控件实现,效果图如下: 后台分页代码如下: #region 私有方法 /// <summary> /// ...
- pcp分布式监控工具
已经集成在redhat6.x版本里 http://pcp.io
- CentOS6下DHCP服务(一)工作原理及安装配置说明
1.DHCP服务用途 DHCP是Dynamic Host Configuration Protocol的简写,DHCP服务器最主要的工作就是自动地将网络参数分配给网络中的每台计算机,让客户端的计算机在 ...
- Python基础学习之序列(1)
序列 序列类型有着相同的访问模式:它的每一个元素可以通过指定一个偏移量的方式得到.而多个元素可以通过切片操作的方式一次得到,下标偏移量是从0开始到总元素-1结束,之所以要减1是因为我们是从0开始计数的 ...