mybatis-cache
- 簡介
- 原始碼解讀
- 快取使用
- 總結&反思
每次在撰寫文章之前自己都喜歡先來個感想,今天的話題是多讀英文檔案,好處多多!什么情況下要考慮快取問題呢?一般微服務架構設計時,分布式應用可能導致臟資料,特別是在秒殺情況下需要特別注意(可以接入外部二級快取)
簡介
檔案中已經很明顯說明了什么cashe,什么是一級快取和二次快取,個人就總結為:快取即相同查詢時,在沒有發生更新、提交、回滾和關閉時,多次查詢不會從資料庫中回傳,直接從Cashe的快取實作類中回傳,一級快取居于sqlsesson快取,二級快取是居于mapper級別快取,啟用一級快取的方式是通過設定localCacheScope=STATEMENT,啟用二級快取的方式是總配置cacheEnabled為true和mapper檔案配置cache
原始碼解讀
- 在構建sqlsessionFactory時,構建Executor,具體構建方法是DefaultSqlSessionFactory#openSessionFromConnection,我們更多關注里面的Configuration#newExecutor
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) { // 這里默認啟用CachingExecutor動態代理
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor); // 執行攔截器
return executor;
}
- 從上面可知我們更多關注的物件是CachingExecutor,下面我們看CachingExecutor來逆推到底如何實作二級快取,
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache(); // 這里是我們關注的二級快取物件,此物件是從MappedStatement中獲取
if (cache != null) {
flushCacheIfRequired(ms); // 這里判斷是否有此快取和是否需要清空快取,若沒有則存放在TransactionalCacheManager#transactionalCaches中
if (ms.isUseCache() && resultHandler == null) { // 判斷是否有Cache啟動
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); // 不啟用快取則查詢本地快取
}
- 從上面快取執行代碼中,我們可以查看到外部快取時從MappedStatement中獲取,接下來我們看這個快取如何構造,查看原始碼可知MappedStatement#cache加入了快取,繼續跟進,則MapperBuilderAssistant#addMappedStatement中加入,再往上一層則MapperBuilderAssistant#useNewCache構建,接下來我們看看構建方法
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace) // 命名空間來構建
.implementation(valueOrDefault(typeClass, PerpetualCache.class)) // 默認PerpetualCache快取,當然這里不支持分布式
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}
此時的Cache的屬性Id為currentNamespace和MappedStatement同時構建,再往上一層發現XMLMapperBuilder#cacheElement方法,最后configurationElement中元素cach判斷是否構建,最后真相大白,實際上二級快取是居于mapper來構建的,XMLMapperBuilder決議xml檔案 -》XMLMapperBuilder#cacheElement欲準備構建-》MapperBuilderAssistant#useNewCache構建cache -》MapperBuilderAssistant#addMappedStatement構建MappedStatement時傳入Cache,其實最終發現解讀mybatis原始碼并不難,
4. 我們發現cache是居于mappedStatement構建的,接下來回歸CachingExecutor如何執行Cache,在講解之前,我們可以看看CacheKey的組成(BaseExecutor#createCacheKey),這里不細談,主要知道Cache的key通過namespace和sql以及查詢條件組成即可,我們來談談如何獲取Cache,我們回傳上面步驟2中的tcm.getObject(cache, key),下面我們詳細看看里面方法.
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
private TransactionalCache getTransactionalCache(Cache cache) {
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
發現每次Cache還有重復利用哦,交給TransactionalCacheManager管理,
5. 當二級快取查找不到時,執行executor,如上面2的delegate.query,
6. 執行查詢時,執行BaseExecutor#query,我們來看看sql的查詢方法:
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 省略
List<E> list;
try {
queryStack++;
// 發現這里是查詢localCache型別,若查不到才去資料庫中查詢,new PerpetualCache("LocalOutputParameterCache")
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();
// 快取物件,判斷是否是STATEMENT,若是,則清空LocalCache
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
接下來真相大白所謂的二級快取就是額外增加的外部快取,實際一級快取也存在;先判斷是否開啟二級快取,若沒有開啟則查詢localCache,若開啟則查詢二級快取并維護,沒有查到時則查詢一級快取,若一級快取也查不到則查詢資料庫,
快取使用
1、默認二級快取不開啟,若想禁用一級快取,則可以設定localCacheScope=STATEMENT,如下:
<settings>
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
2、若想指定sql陳述句清空快取,則通過mapper中設定useCache="false"和flushCache=“true”(執行緒不安全,這里不詳細講解),如下:
<select id="selectTest" resultMap="userTable" useCache="false" flushCache="true" >
select * from user
</select>
具體原理實作代碼flushCache如CachingExecutor#query會重繪快取
3、呼叫EhcacheCache快取
< cache type=“org.mybatis.caches.ehcache.EhcacheCache”/>
總結&反思
- 快取的概念和一級快取以及二級快取的概念
- 快取的使用場景和考慮的問題
- 解讀原始碼的思路,若有更好的方式,請多多指教
- 幾個關鍵類和介面了解:DefaultSqlSessionFactory、Configuration、CachingExecutor、MappedStatement、MapperBuilderAssistant、XMLMapperBuilder、Cache、CacheKey
結合上一篇講解mybatis-plugin,是否覺得mybatis是個很簡單的框架呢?哈哈哈,個人感覺方法只有一個,如何使用方法就是難點,巧妙和熟練使用方法,在遇到相應技術點時就考慮這種類似的問題,其實mybatis是半ORM框架,因為依然存在sql,mybatis強大之處在于強大的動態sql和動態代理模式的設計理念,接下來一篇個人來探討如何搭建一個分布式架構的理念!請稍等!
零散關注背誦點記錄:
資料庫特點:
1、資料結構化 2、資料的共享性高,冗余低,易擴展 3、資料獨立性高 4、統一管理和控制
事務的特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)
概念性理解:臟讀、不可重復讀、幻讀
top指令:rownum和rowid、limit
NOT NULL
UNIQUE
PRIMARY KEY
FOREIGN KEY
CHECK
DEFAULT
-- 創建唯一索引
CREATE UNIQUE INDEX uk_users_name ON t_users(name);
-- 洗掉唯一索引
drop index uk_users_name;
-- 創建唯一鍵約束
ALTER TABLE t_users ADD CONSTRAINT uk_users_name1 UNIQUE (NAME);
ALTER TABLE Orders ADD CONSTRAINT fk_PerOrders FOREIGN KEY (Id_P) REFERENCES Persons(Id_P)
alter table dept2 add primary key(dname);
drop index index_name;
-- 創建序列 Student_stuId_Seq --
create sequence mySeq :序列名
start with 1 :序列開始值
increment by 1 :序列每次自增1
maxvalue 3 :序列最大值
minvalue 1 :序列最小值
cache 2 :每次快取的數值個數
cycle; :是否回圈[cycle:回圈 | nocycle: 不回圈]
JdbcTransactionFactory TransactionIsolationLevel(NONE,Read_uncommit,Read_commit,Reapeatable_read,serializable)
BaseExecutor事務管理JdbcTransaction實作Proxy動態代理(InvocationHandler)
1、掃描xml或者注解檔案,
properties
settings(defaultExecutorType,logImpl)
typeAliases
Plugins(interceptor并實力話)
objectFactory
objectWrapperFactory
reflectorFactory
environments(創建TransactionFactory,datasource)
databaseIdProvider
typeHandlers
mappers
2、創建SqlSessionFactory
3、openSession打開sql快取執行器時 DefaultSqlSessionFactory
1、創建事務工廠(TransationFactory)
2、ManagedTransaction獲取事務管理器,datasource和connection
3、獲取資料庫執行類ExecutorType判斷,SimpleExecutor、ReuseExecutor、BatchExecutor還是
CachingExecutor(cacheEnabled)這里默認開啟一級快取,同一個sqlSession多次時只執行一次,
4、創建DefaultSqlSession
DefaultSqlSession-》CachingExecutor-》BaseExecutor-》 flushCacheIfRequired(ms)
4、創建statementMap,查看是否有sql一樣,如果有看是否關閉,若關閉,則jdbcTransaction中通過datasource獲取連接
這里jdbcTransation管理事務
typeAlias 型別宣告,簡寫之類
TypeHandler型別轉換,jdbc里型別轉換成java型別資料,
defaultObjectFactory主要構造bean,回傳結果集反射之類的和xml決議
Plugin是居于代理模式啟動interceptors,具體流程如下:
1、configuration#addInterceptor決議檔案存放所有攔截器,configuration#newExecutor呼叫代理
2、interceptorChain.pluginAll啟動代理,回傳Executor
3、interceptor.plugin呼叫Plugin.wrap,plugin創建InvocationHandler代理物件,封裝的所有方法攔截器
在對于執行類的所有方法Map<Class,Set<Method>>中,
4、當代理介面類執行時,呼叫先獲取method.getDeclaringClass()對于的宣告類,然后取出攔截器的方法(Map<Class,Set<Method>>)
判斷呼叫方法是否相同if (methods != null && methods.contains(method)),若相同,則執行攔截器
攔截器基本攔截方式:Parameterhandler、ResultSetHandler、StatementHandler、Executor
參考別人面試技術篇:https://www.cnblogs.com/qmillet/p/12523636.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/235465.html
標籤:其他
