最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

前言

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

一、CachingExecutor的在邏輯定位

流程圖中的位置

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

二、CachingExecutor的生效

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

1.全局參數(shù)

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

mybatis.configuration.cache-enabled=true

為什么開了這個參數(shù)就有用呢?其實(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接口的所有方法
  // 當(dāng)然,它同樣也包含某些mapper層次的設(shè)置,如對應(yīng)的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層級進(jìn)行一次緩存配置,也就是所謂的

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

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

我們通過源碼看其實(shí)現(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)看了如何使二級緩存生效,但真正的查詢和存入還沒有細(xì)看,現(xiàn)在來看看其存儲的位置,及如何讀取,我們直接看下源碼

1. 緩存源碼分析

// CachingExecutor.class query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
if (cache != null) {
  // 是否清緩存,即如果Cache配置里含有flushCache=“true” 則進(jìn)行 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 進(jìn)行結(jié)果的存儲
      tcm.putObject(cache, key, list); // issue #578 and #116
    }
    return list;
  }
}

在進(jìn)行下一步前,我們有必要看一下獲取緩存,為什么要傳兩個入?yún)ⅲ碿ache和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其實(shí)就是我們說的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的套娃十分嚴(yán)重,實(shí)際形成了鏈狀引用,而且受配置的影響很大,所以沒有辦法把每一種配置緩存間的相互調(diào)用闡述詳盡。但是我們?nèi)匀豢梢灾v出其核心思想。忽略掉中間的套娃,最終實(shí)現(xiàn)存儲的緩存類為PerpetualCache.class,其包含了一個HashMap,鍵就是我們上面提及的key(混合了方法信息,完整sql等),值為sql返回值的字節(jié)數(shù)組

2. 二級緩存可見性

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

private final Map<Object, Object> entriesToAddOnCommit;

這個變量我們上面源碼分析里其實(shí)說了,就算查數(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級別),這和我們一般的認(rèn)知相悖,因為大多數(shù)緩存層級,都是優(yōu)先查一級緩存,未命中再去查的二級緩存。除了上面因為可見性問題導(dǎo)致的,先查一級緩存,Mybatis里在一二級都開啟的情況下,優(yōu)先使用的是二級緩存的數(shù)據(jù),因此這里需要特別注意。

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

相關(guān)文章

  • Java實(shí)現(xiàn)微信支付的項目實(shí)踐

    Java實(shí)現(xiàn)微信支付的項目實(shí)踐

    最近的一個項目中涉及到了支付業(yè)務(wù),其中用到了微信支付和支付寶支付,本文就來介紹一下Java實(shí)現(xiàn)微信支付的項目實(shí)踐,具有一定的參考價值,感興趣的可以了解一下
    2023-10-10
  • 詳解HTTP請求與響應(yīng)基礎(chǔ)及實(shí)例

    詳解HTTP請求與響應(yīng)基礎(chǔ)及實(shí)例

    這篇文章主要介紹了詳解HTTP請求與響應(yīng)基礎(chǔ)及實(shí)例的相關(guān)資料,這里對http的請求和響應(yīng)進(jìn)行詳細(xì)分析并附有實(shí)現(xiàn)實(shí)例,需要的朋友可以參考下
    2017-07-07
  • Java?Autowired注解深入分析

    Java?Autowired注解深入分析

    @Autowired注解是Spring中非常重要且常見的,接下來就簡要的介紹一下它的用法。@Autowired默認(rèn)是通過set方法,按照類型自動裝配JavaBean,set方法可省略不寫,它主要是修飾在成員變量上
    2023-01-01
  • springboot整合RabbitMQ 中的 TTL實(shí)例代碼

    springboot整合RabbitMQ 中的 TTL實(shí)例代碼

    TTL 是 RabbitMQ 中一個消息或者隊列的屬性,表明一條消息或者該隊列中的所有消息的最大存活時間,單位是毫秒,這篇文章主要介紹了springboot整合RabbitMQ 中的 TTL,需要的朋友可以參考下
    2022-09-09
  • SpringBoot3?Web編程開發(fā)的工程搭建攔截器及測試工具示例

    SpringBoot3?Web編程開發(fā)的工程搭建攔截器及測試工具示例

    這篇文章主要介紹了SpringBoot3?Web編程開發(fā)的工程搭建攔截器及測試工具示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • IDEA更改Terminal的方法步驟

    IDEA更改Terminal的方法步驟

    Windows上開發(fā)有時候cmd不支持bash命令,有些操作就會非常麻煩,本文主要介紹了IDEA更改Terminal的方法步驟,具有一定的參考價值,感興趣的可以了解一下
    2023-09-09
  • Java中對象的轉(zhuǎn)移之序列化與反序列化方式

    Java中對象的轉(zhuǎn)移之序列化與反序列化方式

    文章介紹了序列化與反序列化的定義和實(shí)現(xiàn)流程,重點(diǎn)闡述了Java中的序列化與反序列化實(shí)現(xiàn),包括Serializable接口、Externalizable接口、transient關(guān)鍵字等,并指出了Java原生序列化的缺點(diǎn)及替代方案FastJson和Protobuf,最終總結(jié)了選擇不同序列化工具的場景和優(yōu)勢
    2026-05-05
  • 分布式調(diào)度器之Spring Task 的使用詳解

    分布式調(diào)度器之Spring Task 的使用詳解

    SpringTask是Spring框架中用于任務(wù)調(diào)度的組件,通過簡單的注解就能實(shí)現(xiàn)定時任務(wù)的創(chuàng)建和調(diào)度,可以通過配置線程池來實(shí)現(xiàn),本文給大家介紹分布式調(diào)度器之Spring Task 的使用,感興趣的朋友跟隨小編一起看看吧
    2024-10-10
  • 淺談synchronized加鎖this和class的區(qū)別

    淺談synchronized加鎖this和class的區(qū)別

    synchronized 是 Java 語言中處理并發(fā)問題的一種常用手段,本文主要介紹了synchronized加鎖this和class的區(qū)別,具有一定的參考價值,感興趣的可以了解一下
    2021-11-11
  • Spring AOP 與代理的概念與使用

    Spring AOP 與代理的概念與使用

    大家知道我現(xiàn)在還是一個 CRUD 崽,平時用 AOP 也是 CV 大法。最近痛定思痛,決定研究一下 Spring AOP 的原理。 這里寫一篇文章總結(jié)一下。主要介紹 Java 中 AOP 的實(shí)現(xiàn)原理,最后以兩個簡單的示例來收尾。
    2020-10-10

最新評論

微博| 敦化市| 建湖县| 永兴县| 蓬莱市| 阳信县| 沾化县| 孟州市| 任丘市| 贵阳市| 萍乡市| 林口县| 固安县| 时尚| 通海县| 永胜县| 渑池县| 孟津县| 石城县| 阜平县| 定边县| 南漳县| 南平市| 大田县| 资阳市| 呼和浩特市| 万荣县| 北碚区| 镇沅| 河池市| 遵化市| 林周县| 昂仁县| 黑龙江省| 沅陵县| 简阳市| 昌宁县| 万荣县| 民乐县| 阜新| 普陀区|