SpringBoot整合Ehcache3的實(shí)現(xiàn)步驟
前言
公司部門老項(xiàng)目要遷移升級(jí)java版本,需要進(jìn)行緩存相關(guān)操作,原框架未支持這部分,經(jīng)過調(diào)研java相關(guān)緩存方案大致分為ehcache和redis兩種,redis的value最大值為500mb且超過1mb會(huì)對(duì)存取有性能影響,業(yè)務(wù)系統(tǒng)需要支持列表查詢緩存就不可避免的涉及到大量的數(shù)據(jù)存取過濾,ehcache支持內(nèi)存+磁盤緩存不用擔(dān)心緩存容量問題,所以框架初步版本決定集成ehcache3,設(shè)計(jì)流程結(jié)構(gòu)如下圖所示
緩存配置
maven引用
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency>
個(gè)性化配置
#緩存配置 cache: ehcache: heap: 1000 offheap: 100 disk: 500 diskDir: tempfiles/cache/
@Component @ConfigurationProperties("frmae.cache.ehcache") public class EhcacheConfiguration { ? ? /** ? ? ?* ehcache heap大小 ? ? ?* jvm內(nèi)存中緩存的key數(shù)量 ? ? ?*/ ? ? private int heap; ? ? /** ? ? ?* ehcache offheap大小 ? ? ?* 堆外內(nèi)存大小, 單位: MB ? ? ?*/ ? ? private int offheap; ? ? /** ? ? ?* 磁盤持久化目錄 ? ? ?*/ ? ? private String diskDir; ? ? /** ? ? ?* ehcache disk ? ? ?* 持久化到磁盤的大小, 單位: MB ? ? ?* diskDir有效時(shí)才生效 ? ? ?*/ ? ? private int disk; ? ? public EhcacheConfiguration(){ ? ? ? ? heap = 1000; ? ? ? ? offheap = 100; ? ? ? ? disk = 500; ? ? ? ? diskDir = "tempfiles/cache/"; ? ? } }
代碼注入配置
因?yàn)閟pringboot默認(rèn)緩存優(yōu)先注入redis配置,所以需要手動(dòng)聲明bean進(jìn)行注入,同時(shí)ehcache的value值必須支持序列化接口,不能使用Object代替,這里聲明一個(gè)緩存基類,所有緩存value對(duì)象必須繼承該類
public class BaseSystemObject implements Serializable { }
@Configuration @EnableCaching public class EhcacheConfig { ? ? @Autowired ? ? private EhcacheConfiguration ehcacheConfiguration; ? ? @Autowired ? ? private ApplicationContext context; ? ? @Bean(name = "ehCacheManager") ? ? public CacheManager getCacheManager() { ? ? ? ? //資源池生成器配置持久化 ? ? ? ? ResourcePoolsBuilder resourcePoolsBuilder = ?? ??? ??? ??? ? ?ResourcePoolsBuilder.newResourcePoolsBuilder() ? ? ? ? ? ? ? ? // 堆內(nèi)緩存大小 ? ? ? ? ? ? ? ? .heap(ehcacheConfiguration.getHeap(), EntryUnit.ENTRIES) ? ? ? ? ? ? ? ? // 堆外緩存大小 ? ? ? ? ? ? ? ? .offheap(ehcacheConfiguration.getOffheap(), MemoryUnit.MB) ? ? ? ? ? ? ? ? // 文件緩存大小 ? ? ? ? ? ? ? ? .disk(ehcacheConfiguration.getDisk(), MemoryUnit.MB); ? ? ? ? //生成配置 ? ? ? ? ExpiryPolicy expiryPolicy = ExpiryPolicyBuilder.noExpiration(); ? ? ? ? CacheConfiguration config = CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, BaseSystemObject.class, resourcePoolsBuilder) ? ? ? ? ? ? ? ? //設(shè)置永不過期 ? ? ? ? ? ? ? ? .withExpiry(expiryPolicy) ? ? ? ? ? ? ? ? .build(); ? ? ? ? CacheManagerBuilder cacheManagerBuilder = CacheManagerBuilder.newCacheManagerBuilder() ? ? ? ? ? ? ? ? ?? ??? ? .with(CacheManagerBuilder.persistence(ehcacheConfiguration.getDiskDir())); ? ? ? ? return cacheManagerBuilder.build(true); ? ? } }
緩存操作
緩存預(yù)熱
針對(duì)緩存框架選擇的雙寫策略,即數(shù)據(jù)庫(kù)和緩存同時(shí)寫入,所以在系統(tǒng)啟動(dòng)時(shí)需要預(yù)先將數(shù)據(jù)庫(kù)數(shù)據(jù)加載到緩存中
針對(duì)單表聲明自定義注解,個(gè)性化緩存定義自定義接口
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface HPCache { }
public interface IHPCacheInitService { ? ? String getCacheName(); ? ? void initCache(); }
系統(tǒng)初始化時(shí)同步進(jìn)行緩存初始化,掃描注解實(shí)體類與接口實(shí)現(xiàn)Bean
@Async ? ? public void initCache(Class runtimeClass, List<String> extraPackageNameList) { ? ? ? ? List<Class<?>> cacheEntityList = new ArrayList<>(); ? ? ? ? if (!runtimeClass.getPackage().getName().equals(Application.class.getPackage().getName())) { ? ? ? ? ? ? cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(runtimeClass.getPackage(), HPCache.class)); ? ? ? ? } ? ? ? ? for (String packageName : extraPackageNameList) { ? ? ? ? ? ? cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(packageName, HPCache.class)); ? ? ? ? } ? ? ? ? for (Class clazz : cacheEntityList) { ? ? ? ? ? ? TableName tableName = (TableName) clazz.getAnnotation(TableName.class); ? ? ? ? ? ? List<LinkedHashMap<String, Object>> resultList = commonDTO.selectList(tableName.value(), "*", "1=1", "", new HashMap<>(), false); ? ? ? ? ? ? for (LinkedHashMap<String, Object> map : resultList) { ? ? ? ? ? ? ? ? Cache cache = cacheManager.getCache(clazz.getName(), String.class, BaseSystemObject.class); ? ? ? ? ? ? ? ? String unitguid = ConvertOp.convert2String(map.get("UnitGuid")); ? ? ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? ? ? Object obj = clazz.newInstance(); ? ? ? ? ? ? ? ? ? ? obj = ConvertOp.convertLinkHashMapToBean(map, obj); ? ? ? ? ? ? ? ? ? ? cache.put(unitguid, obj); ? ? ? ? ? ? ? ? } catch (Exception e) { ? ? ? ? ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? } ? ? ? ? //自定義緩存 ? ? ? ? Map<String, IHPCacheInitService> res = context.getBeansOfType(IHPCacheInitService.class); ? ? ? ? for (Map.Entry en : res.entrySet()) { ? ? ? ? ? ? IHPCacheInitService service = (IHPCacheInitService) en.getValue(); ? ? ? ? ? ? service.initCache(); ? ? ? ? } ? ? ? ? System.out.println("緩存初始化完畢"); ? ? }
需要注意,在EhcacheConfig配置類中需要進(jìn)行緩存名稱的提前注冊(cè),否則會(huì)導(dǎo)致操作緩存時(shí)空指針異常
? ? Map<String, Object> annotatedBeans = context.getBeansWithAnnotation(SpringBootApplication.class); ? ? ? ? Class runtimeClass = annotatedBeans.values().toArray()[0].getClass(); ? ? ? ? //do,dao掃描 ? ? ? ? List<String> extraPackageNameList = new ArrayList<String>(); ? ? ? ? extraPackageNameList.add(Application.class.getPackage().getName()); ? ? ? ? List<Class<?>> cacheEntityList = new ArrayList<>(); ? ? ? ? if (!runtimeClass.getPackage().getName().equals(Application.class.getPackage().getName())) { ? ? ? ? ? ? cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(runtimeClass.getPackage(), HPCache.class)); ? ? ? ? } ? ? ? ? for (String packageName : extraPackageNameList) { ? ? ? ? ? ? cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(packageName, HPCache.class)); ? ? ? ? } ? ? ? ? for (Class clazz : cacheEntityList) { ? ? ? ? ? ? cacheManagerBuilder = cacheManagerBuilder.withCache(clazz.getName(), config); ? ? ? ? } ? ? ? ? //自定義緩存 ? ? ? ? Map<String, IHPCacheInitService> res = context.getBeansOfType(IHPCacheInitService.class); ? ? ? ? for (Map.Entry en :res.entrySet()) { ? ? ? ? ? ? IHPCacheInitService service = (IHPCacheInitService)en.getValue(); ? ? ? ? ? ? cacheManagerBuilder = cacheManagerBuilder.withCache(service.getCacheName(), config); ? ? ? ? }
更新操作
手動(dòng)獲取ehcache的bean對(duì)象,調(diào)用put,repalce,delete方法進(jìn)行操作
? ??? ?private ?CacheManager cacheManager = (CacheManager) SpringBootBeanUtil.getBean("ehCacheManager"); ? ? public void executeUpdateOperation(String cacheName, String key, BaseSystemObject value) { ? ? ? ? Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class); ? ? ? ? if (cache.containsKey(key)) { ? ? ? ? ? ? cache.replace(key, value); ? ? ? ? } else { ? ? ? ? ? ? cache.put(key, value); ? ? ? ? } ? ? } ? ? public void executeDeleteOperation(String cacheName, String key) { ? ? ? ? Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class); ? ? ? ? cache.remove(key); ? ? }
查詢操作
緩存存儲(chǔ)單表以主鍵—object形式存儲(chǔ),個(gè)性化緩存為key-object形式存儲(chǔ),單條記錄可以通過getCache方法查詢,列表查詢需要取出整個(gè)緩存按條件進(jìn)行過濾
?public Object getCache(String cacheName, String key){ ? ? ? ? Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class); ? ? ? ? return cache.get(key); ? ? } ? ? public List<Object> getAllCache(String cacheName){ ? ? ? ? List result = new ArrayList<>(); ? ? ? ? Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class); ? ? ? ? Iterator iter = cache.iterator(); ? ? ? ? while (iter.hasNext()) { ? ? ? ? ? ? Cache.Entry entry = (Cache.Entry) iter.next(); ? ? ? ? ? ? result.add(entry.getValue()); ? ? ? ? } ? ? ? ? return result; ? ? }
緩存與數(shù)據(jù)庫(kù)數(shù)據(jù)一致性
數(shù)據(jù)庫(kù)數(shù)據(jù)操作與緩存操作順序?yàn)橄炔僮鲾?shù)據(jù)后操作緩存,在開啟數(shù)據(jù)庫(kù)事務(wù)的情況下針對(duì)單條數(shù)據(jù)單次操作是沒有問題的,如果是組合操作一旦數(shù)據(jù)庫(kù)操作發(fā)生異常回滾,緩存并沒有回滾就會(huì)導(dǎo)致數(shù)據(jù)的不一致,比如執(zhí)行順序?yàn)閐bop1=》cacheop1=》dbop2=》cacheop2,dbop2異常,cacheop1的操作已經(jīng)更改了緩存
這里選擇的方案是在數(shù)據(jù)庫(kù)全部執(zhí)行完畢后統(tǒng)一操作緩存,這個(gè)方案有一個(gè)缺點(diǎn)是如果緩存操作發(fā)生異常還是會(huì)出現(xiàn)上述問題,實(shí)際過程中緩存只是對(duì)內(nèi)存的操作異常概率較小,對(duì)緩存操作持樂觀狀態(tài),同時(shí)我們提供手動(dòng)重置緩存的功能,算是一個(gè)折中方案,下面概述該方案的一個(gè)實(shí)現(xiàn)
聲明自定義緩存事務(wù)注解
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface CacheTransactional { }
聲明切面監(jiān)聽,在標(biāo)記了CacheTransactional注解的方法執(zhí)行前進(jìn)行Redis標(biāo)識(shí),統(tǒng)一執(zhí)行完方法體后執(zhí)行緩存操作
將緩存操作以線程id區(qū)分放入待執(zhí)行隊(duì)列中序列化到redis,提供方法統(tǒng)一操作
public class CacheExecuteModel implements Serializable { ? ? private String obejctClazzName; ? ? private String cacheName; ? ? private String key; ? ? private BaseSystemObject value; ? ? private String executeType; } private ?CacheManager cacheManager = (CacheManager) SpringBootBeanUtil.getBean("ehCacheManager"); ? ? @Autowired ? ? private RedisUtil redisUtil; ? ? public void putCacheIntoTransition(){ ? ? ? ? String threadID = Thread.currentThread().getName(); ? ? ? ? System.out.println("init threadid:"+threadID); ? ? ? ? CacheExecuteModel cacheExecuteModel = new CacheExecuteModel(); ? ? ? ? cacheExecuteModel.setExecuteType("option"); ? ? ? ? redisUtil.redisTemplateSetForCollection(threadID,cacheExecuteModel, GlobalEnum.RedisDBNum.Cache.get_value()); ? ? ? ? redisUtil.setExpire(threadID,5, TimeUnit.MINUTES, GlobalEnum.RedisDBNum.Cache.get_value()); ? ? } ? ? public void putCache(String cacheName, String key, BaseSystemObject value) { ? ? ? ? if(checkCacheOptinionInTransition()){ ? ? ? ? ? ? String threadID = Thread.currentThread().getName(); ? ? ? ? ? ? CacheExecuteModel cacheExecuteModel = new CacheExecuteModel("update", cacheName, key, value.getClass().getName(),value); ? ? ? ? ? ? redisUtil.redisTemplateSetForCollection(threadID,cacheExecuteModel, GlobalEnum.RedisDBNum.Cache.get_value()); ? ? ? ? ? ? redisUtil.setExpire(threadID,5, TimeUnit.MINUTES, GlobalEnum.RedisDBNum.Cache.get_value()); ? ? ? ? }else{ ? ? ? ? ? ? executeUpdateOperation(cacheName,key,value); ? ? ? ? } ? ? } ? ? public void deleteCache(String cacheName, String key) { ? ? ? ? if(checkCacheOptinionInTransition()){ ? ? ? ? ? ? String threadID = Thread.currentThread().getName(); ? ? ? ? ? ? CacheExecuteModel cacheExecuteModel = new CacheExecuteModel("delete", cacheName, key); ? ? ? ? ? ? redisUtil.redisTemplateSetForCollection(threadID,cacheExecuteModel, GlobalEnum.RedisDBNum.Cache.get_value()); ? ? ? ? ? ? redisUtil.setExpire(threadID,5, TimeUnit.MINUTES, GlobalEnum.RedisDBNum.Cache.get_value()); ? ? ? ? }else{ ? ? ? ? ? ? executeDeleteOperation(cacheName,key); ? ? ? ? } ? ? } ? ? public void executeOperation(){ ? ? ? ? String threadID = Thread.currentThread().getName(); ? ? ? ? if(checkCacheOptinionInTransition()){ ? ? ? ? ? ? List<LinkedHashMap> executeList = ?redisUtil.redisTemplateGetForCollectionAll(threadID, GlobalEnum.RedisDBNum.Cache.get_value()); ? ? ? ? ? ? for (LinkedHashMap obj:executeList) { ? ? ? ? ? ? ? ? String executeType = ConvertOp.convert2String(obj.get("executeType")); ? ? ? ? ? ? ? ? if(executeType.contains("option")){ ? ? ? ? ? ? ? ? ? ? continue; ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? String obejctClazzName = ConvertOp.convert2String(obj.get("obejctClazzName")); ? ? ? ? ? ? ? ? String cacheName = ConvertOp.convert2String(obj.get("cacheName")); ? ? ? ? ? ? ? ? String key = ConvertOp.convert2String(obj.get("key")); ? ? ? ? ? ? ? ? LinkedHashMap valueMap = (LinkedHashMap)obj.get("value"); ? ? ? ? ? ? ? ? String valueMapJson = ?JSON.toJSONString(valueMap); ? ? ? ? ? ? ? ? try{ ? ? ? ? ? ? ? ? ? ? Object valueInstance = JSON.parseObject(valueMapJson,Class.forName(obejctClazzName)); ? ? ? ? ? ? ? ? ? ? if(executeType.equals("update")){ ? ? ? ? ? ? ? ? ? ? ? ? executeUpdateOperation(cacheName,key,(BaseSystemObject)valueInstance); ? ? ? ? ? ? ? ? ? ? }else if(executeType.equals("delete")){ ? ? ? ? ? ? ? ? ? ? ? ? executeDeleteOperation(cacheName,key); ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? }catch (Exception e){ ? ? ? ? ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? ? ? redisUtil.redisTemplateRemove(threadID,GlobalEnum.RedisDBNum.Cache.get_value()); ? ? ? ? } ? ? } ? ? public boolean checkCacheOptinionInTransition(){ ? ? ? ? String threadID = Thread.currentThread().getName(); ? ? ? ? System.out.println("check threadid:"+threadID); ? ? ? ? return redisUtil.isValid(threadID, GlobalEnum.RedisDBNum.Cache.get_value()); ? ? }
到此這篇關(guān)于SpringBoot整合Ehcache3的實(shí)現(xiàn)步驟的文章就介紹到這了,更多相關(guān)SpringBoot整合Ehcache3內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring中FactoryBean中的getObject()方法實(shí)例解析
這篇文章主要介紹了spring中FactoryBean中的getObject()方法實(shí)例解析,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02舉例說明Java設(shè)計(jì)模式編程中ISP接口隔離原則的使用
這篇文章主要介紹了Java設(shè)計(jì)模式編程中ISP接口隔離原則的使用,接口隔離原則主張一個(gè)類對(duì)另外一個(gè)類的依賴性應(yīng)當(dāng)是建立在最小的接口上,需要的朋友可以參考下2016-02-02詳解Java對(duì)象的強(qiáng)、軟、弱和虛引用+ReferenceQueue
這篇文章主要介紹了詳解Java對(duì)象的強(qiáng)、軟、弱和虛引用+ReferenceQueue的相關(guān)資料,需要的朋友可以參考下2017-06-06SpringBoot集成阿里巴巴Druid監(jiān)控的示例代碼
這篇文章主要介紹了SpringBoot集成阿里巴巴Druid監(jiān)控的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-04-04Java 實(shí)戰(zhàn)范例之精美網(wǎng)上音樂平臺(tái)的實(shí)現(xiàn)
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+vue+Springboot+ssm+mysql+maven+redis實(shí)現(xiàn)一個(gè)前后端分離的精美網(wǎng)上音樂平臺(tái),大家可以在過程中查缺補(bǔ)漏,提升水平2021-11-11mybatis?@InsertProvider報(bào)錯(cuò)問題及解決
這篇文章主要介紹了mybatis?@InsertProvider報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07