淺談Spring boot cache使用和原理
緩存要解決的問題:一個程序的瓶頸在于數(shù)據(jù)庫,我們也知道內(nèi)存的速度是大大快于硬盤的速度的。當(dāng)我們需要重復(fù)地獲取相同的數(shù)據(jù)的時候,我們一次又一次的請求數(shù)據(jù)庫或者遠(yuǎn)程服務(wù),導(dǎo)致大量的時間耗費在數(shù)據(jù)庫查詢或者遠(yuǎn)程方法調(diào)用上,導(dǎo)致程序性能的惡化,這便是數(shù)據(jù)緩存要解決的問題。
類似的緩存技術(shù)有:Redis、EhCache、Guava等,現(xiàn)在一般常用的為Redis。
Spring 3.1 引入了激動人心的基于注釋(annotation)的緩存(cache)技術(shù),它本質(zhì)上不是一個具體的緩存實現(xiàn)方案(例如EHCache 或者 OSCache),而是一個對緩存使用的抽象,通過在既有代碼中添加少量它定義的各種 annotation,即能夠達(dá)到緩存方法的返回對象的效果。
Spring 的緩存技術(shù)還具備相當(dāng)?shù)撵`活性,不僅能夠使用 SpEL(Spring Expression Language)來定義緩存的 key 和各種 condition,還提供開箱即用的緩存臨時存儲方案,也支持和主流的專業(yè)緩存例如 EHCache 集成。
其特點總結(jié)如下:
1. 通過少量的配置 annotation 注釋即可使得既有代碼支持緩存
2. 支持開箱即用 Out-Of-The-Box,即不用安裝和部署額外第三方組件即可使用緩存
3. 支持 Spring Express Language,能使用對象的任何屬性或者方法來定義緩存的 key 和 condition
4. 支持 AspectJ,并通過其實現(xiàn)任何方法的緩存支持
5. 支持自定義 key 和自定義緩存管理者,具有相當(dāng)?shù)撵`活性和擴展性
一、Spring boot cache原理
第一步、自動配置類;
自動啟動類:CacheAutoConfiguration
屬性配置:CacheProperties
主啟動類添加:@EnableCaching注解
cache POM添加:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
第二步、從緩存的配置類 中獲取 多個cache
CacheConfigurationImportSelector.selectImports()方法獲取
static class CacheConfigurationImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { CacheType[] types = CacheType.values(); String[] imports = new String[types.length]; for (int i = 0; i < types.length; i++) { imports[i] = CacheConfigurations.getConfigurationClass(types[i]); } return imports; } }
獲取結(jié)果:SimpleCacheConfiguration 默認(rèn)cache
org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration【默認(rèn)】 org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
第三步:SimpleCacheConfiguration.cacheManager()
此方法中給容器中注冊了一個CacheManager組件:類型為ConcurrentMapCacheManager
@Bean public ConcurrentMapCacheManager cacheManager() { ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager(); List<String> cacheNames = this.cacheProperties.getCacheNames(); if (!cacheNames.isEmpty()) { cacheManager.setCacheNames(cacheNames); } return this.customizerInvoker.customize(cacheManager); }
第四步:查看獲取緩存方法getCache()
ConcurrentMapCacheManager 類里,數(shù)據(jù)都存儲到為ConcurrentMap 中
public Cache getCache(String name) { Cache cache = this.cacheMap.get(name); //cacheMap 為ConcurrentMap 類型,獲取一個cache組件 if (cache == null && this.dynamic) { synchronized (this.cacheMap) { cache = this.cacheMap.get(name); //cahceMap不為空獲取 if (cache == null) { //可以獲取或者創(chuàng)建ConcurrentMapCache類型的緩存組件;他的作用將數(shù)據(jù)保存在ConcurrentMap中; cache = createConcurrentMapCache(name); this.cacheMap.put(name, cache); //ConcurrentMapCache.lookup(); } } } return cache; }
二、Cacheable運行流程:
@Cacheable: 1、方法運行之前,先去查詢Cache(緩存組件),按照cacheNames指定的名字獲?。?(CacheManager先獲取相應(yīng)的緩存),第一次獲取緩存如果沒有Cache組件會自動創(chuàng)建。 2、去Cache中查找緩存的內(nèi)容(ConcurrentMapCache.lookup()方法中去查找),使用一個key,默認(rèn)就是方法的參數(shù); key是按照某種策略生成的;默認(rèn)是使用keyGenerator生成的,默認(rèn)使用SimpleKeyGenerator生成key; SimpleKeyGenerator生成key的默認(rèn)策略; 如果沒有參數(shù);key=new SimpleKey(); 如果有一個參數(shù):key=參數(shù)的值 如果有多個參數(shù):key=new SimpleKey(params);
//這個方法 SimpleKeyGenerator.generateKey() 方法生成key public static Object generateKey(Object... params) { if (params.length == 0) { return SimpleKey.EMPTY; } if (params.length == 1) { //如果只有一個參數(shù),直接返回這個參數(shù)為key Object param = params[0]; if (param != null && !param.getClass().isArray()) { return param; } } return new SimpleKey(params); }
3、沒有查到緩存就調(diào)用目標(biāo)方法; 4、將目標(biāo)方法返回的結(jié)果,放進(jìn)緩存中ConcurrentMapCache.put();
@Cacheable標(biāo)注的方法執(zhí)行之前先來檢查緩存中有沒有這個數(shù)據(jù),默認(rèn)按照參數(shù)的值作為key去查詢緩存, 如果沒有就運行方法并將結(jié)果放入緩存;以后再來調(diào)用就可以直接使用緩存中的數(shù)據(jù);
核心: 1)、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】組件 2)、key使用keyGenerator生成的,默認(rèn)是SimpleKeyGenerator
詳細(xì)執(zhí)行流程:ConcurrentMapCache.lookup()上斷點查看,執(zhí)行過程
//第一步CacheAspectSupport 中execute() private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) //第二步 CacheAspectSupport private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) { Object result = CacheOperationExpressionEvaluator.NO_RESULT; for (CacheOperationContext context : contexts) { if (isConditionPassing(context, result)) { Object key = generateKey(context, result); //獲取key Cache.ValueWrapper cached = findInCaches(context, key); if (cached != null) { return cached; } else { if (logger.isTraceEnabled()) { logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames()); } } } } return null; } //第三步:CacheAspectSupport.findInCaches() //第四步:AbstractCacheInvoker.doGet() //第五步:AbstractValueAdaptingCache.get(); @Override public ValueWrapper get(Object key) { Object value = lookup(key); return toValueWrapper(value); } // 第六步:ConcurrentMapCache.lookup(); 從ConcurrentMap 中根據(jù)key獲取值 @Override protected Object lookup(Object key) { return this.store.get(key); }
三、Cacheable 注解的幾個屬性:
1、cacheNames/value:指定緩存組件的名字;將方法的返回結(jié)果放在哪個緩存中,是數(shù)組的方式,可以指定 多個緩存;
2、key:緩存數(shù)據(jù)使用的key;可以用它來指定。默認(rèn)是使用方法參數(shù)的值 1-方法的返回值
編寫SpEL; #i d;參數(shù)id的值 #a0 #p0 #root.args[0]
getEmp[2]
3、keyGenerator:key的生成器;可以自己指定key的生成器的組件id
key/keyGenerator:二選一使用;
4、cacheManager:指定緩存管理器;或者cacheResolver指定獲取解析器
5、condition:指定符合條件的情況下才緩存;
,condition = "#id>0"
condition = "#a0>1":第一個參數(shù)的值》1的時候才進(jìn)行緩存
6、unless:否定緩存;當(dāng)unless指定的條件為true,方法的返回值就不會被緩存;可以獲取到結(jié)果進(jìn)行判斷
unless = "#result == null"
unless = "#a0==2":如果第一個參數(shù)的值是2,結(jié)果不緩存;
7、sync:是否使用異步模式;異步模式的情況下unless不支持
四、Cache使用:
1.Cacheable的使用
@Cacheable(value = {"emp"}/*,keyGenerator = "myKeyGenerator",condition = "#a0>1",unless = "#a0==2"*/) public Employee getEmp(Integer id){ System.out.println("查詢"+id+"號員工"); Employee emp = employeeMapper.getEmpById(id); return emp; }
2.自定義keyGenerator:
@Bean("myKeyGenerator") public KeyGenerator keyGenerator(){ return new KeyGenerator(){ @Override public Object generate(Object target, Method method, Object... params) { return method.getName()+"["+ Arrays.asList(params).toString()+"]"; } }; }
3.CachePut的使用:更新緩存
/** * @CachePut:既調(diào)用方法,又更新緩存數(shù)據(jù);同步更新緩存 * 修改了數(shù)據(jù)庫的某個數(shù)據(jù),同時更新緩存; * 運行時機: * 1、先調(diào)用目標(biāo)方法 * 2、將目標(biāo)方法的結(jié)果緩存起來 * * 測試步驟: * 1、查詢1號員工;查到的結(jié)果會放在緩存中; * key:1 value:lastName:張三 * 2、以后查詢還是之前的結(jié)果 * 3、更新1號員工;【lastName:zhangsan;gender:0】 * 將方法的返回值也放進(jìn)緩存了; * key:傳入的employee對象 值:返回的employee對象; * 4、查詢1號員工? * 應(yīng)該是更新后的員工; * key = "#employee.id":使用傳入的參數(shù)的員工id; * key = "#result.id":使用返回后的id * @Cacheable的key是不能用#result * 為什么是沒更新前的?【1號員工沒有在緩存中更新】 * */ @CachePut(value = "emp",key = "#result.id") public Employee updateEmp(Employee employee){ System.out.println("updateEmp:"+employee); employeeMapper.updateEmp(employee); return employee; }
4.CacheEvict 緩存清除
/** * @CacheEvict:緩存清除 * key:指定要清除的數(shù)據(jù) * allEntries = true:指定清除這個緩存中所有的數(shù)據(jù) * beforeInvocation = false:緩存的清除是否在方法之前執(zhí)行 * 默認(rèn)代表緩存清除操作是在方法執(zhí)行之后執(zhí)行;如果出現(xiàn)異常緩存就不會清除 * * beforeInvocation = true: * 代表清除緩存操作是在方法運行之前執(zhí)行,無論方法是否出現(xiàn)異常,緩存都清除 * * */ @CacheEvict(value="emp",beforeInvocation = true,key = "#id") public void deleteEmp(Integer id){ System.out.println("deleteEmp:"+id); //employeeMapper.deleteEmpById(id); int i = 10/0; }
5.Caching 復(fù)雜配置
// @Caching 定義復(fù)雜的緩存規(guī)則 @Caching( cacheable = { @Cacheable(/*value="emp",*/key = "#lastName") }, put = { @CachePut(/*value="emp",*/key = "#result.id"), @CachePut(/*value="emp",*/key = "#result.email") } ) public Employee getEmpByLastName(String lastName){ return employeeMapper.getEmpByLastName(lastName); }
6.CacheConfig緩存清除
@CacheConfig(cacheNames="emp",cacheManager = "employeeCacheManager") //抽取緩存的公共配置 @Service public class EmployeeService {
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
java使用hashMap緩存保存數(shù)據(jù)的方法
這篇文章主要介紹了java使用hashMap緩存保存數(shù)據(jù)的方法,結(jié)合實例形式簡單分析了java基于hashmap讀寫緩存數(shù)據(jù)的相關(guān)操作技巧,需要的朋友可以參考下2016-08-08SpringBoot實現(xiàn)多個ApplicationRunner時部分接口未執(zhí)行問題
這篇文章主要介紹了SpringBoot實現(xiàn)多個ApplicationRunner時部分接口未執(zhí)行問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05詳解用Spring Boot零配置快速創(chuàng)建web項目
本篇文章主要介紹了詳解用Spring Boot零配置快速創(chuàng)建web項目,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-03-03詳細(xì)介紹idea如何設(shè)置類頭注釋和方法注釋(圖文)
本篇文章主要介紹了idea如何設(shè)置類頭注釋和方法注釋(圖文),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12QTabWidget標(biāo)簽實現(xiàn)雙擊關(guān)閉的方法(推薦)
這篇文章主要介紹了QTabWidget標(biāo)簽實現(xiàn)雙擊關(guān)閉的方法(推薦)的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-06-06Springboot2.x結(jié)合Mabatis3.x下Hikari連接數(shù)據(jù)庫報超時錯誤
本文針對Springboot2.x與Mybatis3.x結(jié)合使用時,Hikari連接數(shù)據(jù)庫出現(xiàn)超時錯誤的問題進(jìn)行了深入分析,并提供了一系列有效的解決方法,感興趣的可以了解一下2023-11-11