欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Mybatis CachingExecutor二級緩存使用示例詳解

 更新時間:2023年09月26日 08:28:56   作者:戰(zhàn)斧  
這篇文章主要介紹了?Mybatis的CachingExecutor與二級緩存使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

上次我們講Mybatis的緩存時,我們提到了CachingExecutor,知道了這個帶緩存的執(zhí)行器就是二級緩存的來源,這次我們系統(tǒng)的分析下其是如何產(chǎn)生作用的

一、CachingExecutor的在邏輯定位

流程圖中的位置

我把CachingExecutor在邏輯鏈路中的位置標出來了,就是儲存在會話對象中,通過會話可使用到CachingExecutor,而CachingExecutor又內(nèi)置一個SimpleExecutor,熟悉設(shè)計模式的同學應該知道這就是所謂的委派模式。當然,這里面會話內(nèi)置的也可能直接就是SimpleExecutor了,那樣的話,調(diào)用的就直接是SimpleExecutor執(zhí)行器了。

二、CachingExecutor的生效

既然知道了CachingExecutor在會話對象中,那毫無疑問,就是在創(chuàng)建會話的時候,把一個CachingExecutor放入到會話對象中的,我們來看看,要實現(xiàn)這個目標要做什么

1.全局參數(shù)

首先,要想啟用CachingExecutor,我們得開啟一個全局的設(shè)置參數(shù)

mybatis.configuration.cache-enabled=true

為什么開了這個參數(shù)就有用呢?其實不難猜想,其作用的位置肯定還是在創(chuàng)建會話對象的時候,我們直接看源碼吧

SqlSessionUtils.class

// SqlSessionUtils.class
// 通過工廠對象(單例,存在容器中),開啟會話,獲得會話對象,executorType是執(zhí)行器枚舉類(SIMPLE, REUSE, BATCH),一般是SIMPLE
session = sessionFactory.openSession(executorType);

DefaultSqlSessionFactory.class

// DefaultSqlSessionFactory.class
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);
    // 通過configuration(Mybatis配置,單例)創(chuàng)建執(zhí)行器
    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.class

// Configuration.class
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 {
    // 創(chuàng)建了一個簡易執(zhí)行器
    executor = new SimpleExecutor(this, transaction);
  }
  // 如果開啟了緩存,即 mybatis.configuration.cache-enabled=true
  if (cacheEnabled) {
    // 創(chuàng)建了緩存執(zhí)行器,并且把簡易執(zhí)行器作為其 delegate,通過構(gòu)造方法放入
    executor = new CachingExecutor(executor);
  }
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

如上,可以看出CachingExecutor就是由 mybatis.configuration.cache-enabled=true 開啟的,并且和會話的情況無關(guān),這是一個全局設(shè)置,一旦開啟,所有會話都會首先調(diào)用CachingExecutor

2. MappedStatement啟用Cache

是不是我們啟用了CachingExecutor就可以使用二級緩存了呢?為什么這么說,我們還是直接看CachingExecutor的源碼
CachingExecutor.class

// CachingExecutor.class
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
  // mappedStatement 就是我們寫的mapper接口里的某個方法的所有信息,注意其描述的是一個方法,而不是一整個mapper接口的所有方法
  // 當然,它同樣也包含某些mapper層次的設(shè)置,如對應的xml文件位置等,此處的Cache同樣是mapper層次的設(shè)置
  Cache cache = ms.getCache();
  // 有Cache才會真正的去找二級緩存,否則直接就讓委托的執(zhí)行器去查詢數(shù)據(jù)了。
  // 注意此處的Cache并不是緩存本身,而是mapper里的緩存配置
  if (cache != null) {
    // ....
      List<E> list = (List<E>) tcm.getObject(cache, key);
    // ....
      return list;
    }
  }
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

可以看到,要想真正啟用緩存,還得在Mapper層級進行一次緩存配置,也就是所謂的

聲明下Cache時可以設(shè)置一下參數(shù)
<cache eviction="FIFO" flushinterval="60000" size="512" readOnly="true"/>
當然也可以不進行任何參數(shù)配置,就單獨聲明下Cache,如下:
<cache/>

我們看一下此時MappedStatement里Cache的構(gòu)成,可以說是套娃巔峰,把委派模式玩到了極致

我們通過源碼看其實現(xiàn)

MapperBuilderAssistant.class

// MapperBuilderAssistant.class
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))
      .addDecorator(valueOrDefault(evictionClass, LruCache.class))
      .clearInterval(flushInterval)
      .size(size)
      .readWrite(readWrite)
      .blocking(blocking)
      .properties(props)
      .build();
  configuration.addCache(cache);
  currentCache = cache;
  return cache;
}

三、二級緩存的存取

上面我們已經(jīng)看了如何使二級緩存生效,但真正的查詢和存入還沒有細看,現(xiàn)在來看看其存儲的位置,及如何讀取,我們直接看下源碼

1. 緩存源碼分析

// CachingExecutor.class query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
if (cache != null) {
  // 是否清緩存,即如果Cache配置里含有flushCache=“true” 則進行 tcm.clear();
  flushCacheIfRequired(ms);
  if (ms.isUseCache() && resultHandler == null) {
    ensureNoOutParams(ms, boundSql);
    @SuppressWarnings("unchecked")
    // tcm 是一個成員變量,是通過new TransactionalCacheManager()賦值的,此處為查詢
    List<E> list = (List<E>) tcm.getObject(cache, key);
    if (list == null) {
      list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      // 通過 tcm 進行結(jié)果的存儲
      tcm.putObject(cache, key, list); // issue #578 and #116
    }
    return list;
  }
}

在進行下一步前,我們有必要看一下獲取緩存,為什么要傳兩個入?yún)?,即cache和key?

  • cache:mapper級別的緩存配置及二級緩存
  • key:方法的全限定名及完整的sql即sql入?yún)?/li>

接下來,我們不難發(fā)現(xiàn)已經(jīng)出現(xiàn)了 存儲、獲取、清除這三種方法,且都圍繞著tcm(TransactionalCacheManager),因此接下來,我們還得關(guān)注一下TransactionalCacheManager。

public class TransactionalCacheManager {
	private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
	public Object getObject(Cache cache, CacheKey key) {
	    // 先把緩存設(shè)置作為key,查詢出一個TransactionalCache
    	return getTransactionalCache(cache).getObject(key);
  	}
  	private TransactionalCache getTransactionalCache(Cache cache) {
  	    // 作為第一次查詢,不難發(fā)現(xiàn),所謂的value居然也是依托緩存設(shè)置來構(gòu)造的
    	return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
    }
  	// ....
}
public class TransactionalCache implements Cache {
    private final Cache delegate;
    private boolean clearOnCommit;
    // 臨時待加緩存,查詢數(shù)據(jù)庫返回的結(jié)果,首先會放在這里,等本次事務(wù)提交后,才會加入到真正的二級緩存中,即調(diào)用套娃對象,層層深入,最終存入HashMap
    // 在事務(wù)提交前,其他會話甚至本會話自己都無法看見該緩存,更無法使用該緩存
    private final Map<Object, Object> entriesToAddOnCommit;
    // 查二級緩存沒查到時,會把key值存在這個miss的集合中
    private final Set<Object> entriesMissedInCache;
    public TransactionalCache(Cache delegate) {
    	this.delegate = delegate;
    	this.clearOnCommit = false;
    	this.entriesToAddOnCommit = new HashMap<>();
    	this.entriesMissedInCache = new HashSet<>();
    }
	public Object getObject(Object key) {
		// issue #116
		// 觀察后,可以知道,這里的delegate其實就是我們說的mapper緩存設(shè)置
		// 由此可見,緩存設(shè)置中也能包含緩存的值,并且以完整sql為鍵,sql結(jié)果為值以map存儲
		Object object = delegate.getObject(key);
		if (object == null) {
			entriesMissedInCache.add(key);
		}
		// issue #146
		if (clearOnCommit) {
			return null;
		} else {
			return object;
		}
	}
}

由于Cache的套娃十分嚴重,實際形成了鏈狀引用,而且受配置的影響很大,所以沒有辦法把每一種配置緩存間的相互調(diào)用闡述詳盡。但是我們?nèi)匀豢梢灾v出其核心思想。忽略掉中間的套娃,最終實現(xiàn)存儲的緩存類為PerpetualCache.class,其包含了一個HashMap,鍵就是我們上面提及的key(混合了方法信息,完整sql等),值為sql返回值的字節(jié)數(shù)組

2. 二級緩存可見性

二級緩存并不是即時生效的,我們可以關(guān)注下TransactionalCache 類,這個類看名字也知道是事務(wù)有關(guān),它有一個成員變量

private final Map<Object, Object> entriesToAddOnCommit;

這個變量我們上面源碼分析里其實說了,就算查數(shù)據(jù)庫,返回了結(jié)果,也不是立即就到我們說的終極位置——PerpetualCache的HashMap里。而是在這個變量里暫存,等待事務(wù)提交了,再把這里的數(shù)據(jù)存入真正的二級緩存處

而在此之前,即使是本會話,也沒法從二級緩存中撈到東西,也就是說在一個事務(wù)里,你連續(xù)執(zhí)行兩次同樣的sql,盡管二級緩存已經(jīng)暫存了數(shù)據(jù),但第二次sql經(jīng)過CachingExecutor時,它并不會把這個數(shù)據(jù)給你,那么自然,其他的會話也是無法看見這個數(shù)據(jù)的。

因此,我們說二級緩存的數(shù)據(jù),只在本事務(wù)提交后才正式可見,在此之前,其他會話甚至本會話自己,都無法使用該二級緩存

3. 一二級緩存優(yōu)先級

看上圖,不難明白,如果開啟了二級緩存,則先查的是二級緩存(mapper級別),這和我們一般的認知相悖,因為大多數(shù)緩存層級,都是優(yōu)先查一級緩存,未命中再去查的二級緩存。除了上面因為可見性問題導致的,先查一級緩存,Mybatis里在一二級都開啟的情況下,優(yōu)先使用的是二級緩存的數(shù)據(jù),因此這里需要特別注意。

以上就是 Mybatis的CachingExecutor與二級緩存的詳細內(nèi)容,更多關(guān)于 Mybatis的CachingExecutor與二級緩存的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 淺聊java8中數(shù)值流的使用

    淺聊java8中數(shù)值流的使用

    java8為我提供的簡單快捷的數(shù)值流計算API,本文就基于幾個常見的場景介紹一下數(shù)值流API的使用,文中的示例代碼講解詳細,感興趣的小伙伴可以了解下
    2023-10-10
  • Mybatis-Spring連接mysql 8.0配置步驟出錯的解決方法

    Mybatis-Spring連接mysql 8.0配置步驟出錯的解決方法

    這篇文章主要為大家詳細介紹了Mybatis-Spring連接mysql 8.0配置步驟出錯的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-06-06
  • ArrayList底層操作機制源碼解析

    ArrayList底層操作機制源碼解析

    這篇文章主要介紹了ArrayList底層操作機制源碼解析,當創(chuàng)建ArrayList對象時,如果使用的是無參構(gòu)造器,則初始elementData容量為0,第1次添加,則擴容elementData為10,如需要再次擴容,則擴容elementData為1.5倍,需要的朋友可以參考下
    2023-09-09
  • 詳解SpringMVC使用MultipartFile實現(xiàn)文件的上傳

    詳解SpringMVC使用MultipartFile實現(xiàn)文件的上傳

    本篇文章主要介紹了SpringMVC使用MultipartFile實現(xiàn)文件的上傳,本地的文件上傳到資源服務(wù)器上,比較好的辦法就是通過ftp上傳。這里是結(jié)合SpringMVC+ftp的形式上傳的,有興趣的可以了解一下。
    2016-12-12
  • Java AbstractMethodError原因案例詳解

    Java AbstractMethodError原因案例詳解

    這篇文章主要介紹了Java AbstractMethodError原因案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • Mybatis配置之<environments>配置元素詳解

    Mybatis配置之<environments>配置元素詳解

    這篇文章主要介紹了Mybatis配置之<environments>配置元素,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Idea安裝Eslint插件提示:Plugin NativeScript was not installed的問題

    Idea安裝Eslint插件提示:Plugin NativeScript was not installed的問題

    這篇文章主要介紹了Idea安裝Eslint插件提示:Plugin NativeScript was not installed的問題,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-10-10
  • Java項目防止SQL注入的幾種方法總結(jié)

    Java項目防止SQL注入的幾種方法總結(jié)

    SQL注入是比較常見的網(wǎng)絡(luò)攻擊方式之一,在客戶端在向服務(wù)器發(fā)送請求的時候,sql命令通過表單提交或者url字符串拼接傳遞到后臺持久層,最終達到欺騙服務(wù)器執(zhí)行惡意的SQL命令,下面這篇文章主要給大家總結(jié)介紹了關(guān)于Java項目防止SQL注入的幾種方法,需要的朋友可以參考下
    2023-04-04
  • IDEA的常見的設(shè)置和優(yōu)化功能圖文詳解

    IDEA的常見的設(shè)置和優(yōu)化功能圖文詳解

    這篇文章主要介紹了IDEA的常見的設(shè)置和優(yōu)化功能,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • 基于Java 談回調(diào)函數(shù)

    基于Java 談回調(diào)函數(shù)

    回調(diào)函數(shù)就是一個通過函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個函數(shù),當這個指針被用來調(diào)用其所 指向的函數(shù)時,我們就說這是回調(diào)函數(shù)
    2017-05-05

最新評論