[TOC]
簡介
上一章節(jié)我們簡單了解了二級緩存的配置设江。今天我們詳細分析下二級緩存以及為什么不建議使用二級緩存悦荒。
一級緩存針對的是sqlsession瓮顽。二級緩存針對的是namespace層面的泌类。
配置
- 之前我們已經(jīng)提到了配置二級緩存以及配置自定義的二級緩存读跷。下面我們從頭開始實現(xiàn)二級緩存。
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);
return executor;
}
```
- 通過上面的代碼我們可以看出來蠢络,`cacheEnabled`這個屬性是控制二級緩存的配置的衰猛。而這個屬性在Configuration中默認是true。這里說明了mybatis默認是開啟緩存功能的刹孔。二級緩存和一級緩存的區(qū)別其實除了范圍以外啡省,他們的不同點就是順序不同。真正開啟二級緩存的是在mapper的xml中配置cache標(biāo)簽就行了髓霞。

- 我們這里在StudentMapper.xml中配置.然后我們在test類中進行獲取兩次sqlsession調(diào)用同一個sql.
```java
SqlSession sqlSession = SqlSessionFactoryUtils.openSqlsession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.getStudentByIdAndName("1", "1");
System.out.println(student);
SqlSession sqlSession1 = SqlSessionFactoryUtils.sqlSessionFactory.openSession();
StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class);
Student studentByIdAndName = mapper1.getStudentByIdAndName("1", "1");
System.out.println(studentByIdAndName);
- 但是結(jié)果確實很意外卦睹。事實上并沒有只調(diào)用一次sql。而是調(diào)用了兩次酸茴。這僅僅是結(jié)果上的異常分预。我們用的是Student這個結(jié)果接受的。我們再從代碼層面上看看
@Data
@Builder
@Accessors(chain = true)
public class Student {
/**
* 學(xué)生索引id
* */
* private String id;
* /**
* * 姓名
* */
* private String userName;
/**
* 用戶昵稱
* */
* private String userNick;
/**
* 年齡
* */
* private Integer age;
* /**
* * 性別 true : 男 薪捍; false : 女
* */
* private SexEnum sex;
* /**
* * 生日
* */
* private Date birth;
* /**
* * 身高
* */
* private Double height;
* }
- 細心的伙伴也許能夠發(fā)現(xiàn)笼痹。我們這個實體并沒有實現(xiàn)序列化配喳。但是之前我們已經(jīng)說過了二級緩存的實體需要序列化。按道理來說應(yīng)該報錯的凳干。這就說明我們二級緩存開啟晴裹,或者確切的說應(yīng)該說是二級緩存沒有起到作用。
- 那么我們先將實體進行序列化救赐。然后啟動發(fā)現(xiàn)并沒有任何效果涧团。我們來看看
CacheingExecutor.commit()
這個方法里面有事物的提交tcm.commit()
。
- 那么我們先將實體進行序列化救赐。然后啟動發(fā)現(xiàn)并沒有任何效果涧团。我們來看看
- 這個地方就是進行緩存存儲的经磅。我們再來看看mybatis是如何解析mapper.xml中配置的cache標(biāo)簽的泌绣。
- 由上面代碼我們得知mybatis會創(chuàng)建一個緩存對象。里面具體是通過一個build方法來創(chuàng)建的预厌。我們在來看看build方法里是啥東西阿迈。
- setStandardDecorators這個方法我們不知道做啥的。但是熟悉設(shè)計模式的都知道Decorator這個詞是裝飾者模式轧叽。這里這個方法也是用來裝飾用的苗沧。看看mybatis為我們裝飾了那些東西炭晒。
- 首先在newBaseCacheInstance方法中創(chuàng)建原始對象PreprtualCache.然后是加載默認提供的回收機制用的Cache待逞。這個實在build前設(shè)置的。
- 然后就是通過setStandardDecorators進行裝飾了网严。
- 所以他的裝飾鏈為:SynchronizedCache->LogginCache->SerializedCache->LruCache->PerPetualCache
- 而在上面的tcm.commit就是在SerializedCache進行緩存對象的识樱。所以我們之前的代碼是sqlsession沒有提交。所以代碼只要稍微改動下震束。
SqlSession sqlSession = SqlSessionFactoryUtils.openSqlsession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.getStudentByIdAndName("1", "1");
System.out.println(student);
sqlSession.commit();
SqlSession sqlSession1 = SqlSessionFactoryUtils.sqlSessionFactory.openSession();
StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class);
Student studentByIdAndName = mapper1.getStudentByIdAndName("1", "1");
System.out.println(studentByIdAndName);
SynchronizedCache : 同步Cache.這個類就是保證線程安全牺荠。所以他的方法基本上是加上
synchronized
來保證線程安全的。LoggingCache : 日志驴一。在上面我們有個日志是Cache Hit Ratio 0.5 表示二級緩存的命中率。
SerializedCache : 就是用來序列化數(shù)據(jù)的灶壶。
LruCache : 回收cache的算法
PerPetualCache :基本Cache .
源碼
CachingExecutor
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//獲取Cache對象
Cache cache = ms.getCache();
if (cache != null) {
//根據(jù)statment配置刷新緩存肝断,默認是insert、update驰凛、delete會刷新緩存
flushCacheIfRequired(ms);
//二級緩存開啟入口胸懈。
if (ms.isUseCache() && resultHandler == null) {
//這個方法主要用來處理存儲過程。后續(xù)章節(jié)說明
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
//通過緩存事物查詢數(shù)據(jù)
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
//調(diào)用委托類查詢數(shù)據(jù)
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//加入緩存恰响,供下次獲取
tcm.putObject(cache, key, list);
}
return list;
}
}
//沒有開啟二級緩存則繼續(xù)往下走
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
```
# 缺點
- 二級緩存因為更加廣泛趣钱,所以容易造成臟數(shù)據(jù)。尤其是在關(guān)聯(lián)查詢的時候有序無法控制刷新力度胚宦。很容易出現(xiàn)臟讀首有。

# 自定義二級緩存
- 在之前我們了解到的`PerpetualCache`是緩存鏈上最基本的緩存類燕垃。我們自定義的緩存就是替代這個類的。在mybatis中會現(xiàn)根據(jù)我們注冊進來的類進行實例化井联。如果沒有則用默認的`PerpetualCache`這個類作為基礎(chǔ)緩存類卜壕。
-
[加入戰(zhàn)隊](#addMe)
# # <span id="addMe">加入戰(zhàn)隊</span>
## 微信公眾號
