SpringBoot整合Ehcache3的實現(xiàn)步驟
前言
公司部門老項目要遷移升級java版本,需要進行緩存相關操作,原框架未支持這部分,經(jīng)過調研java相關緩存方案大致分為ehcache和redis兩種,redis的value最大值為500mb且超過1mb會對存取有性能影響,業(yè)務系統(tǒng)需要支持列表查詢緩存就不可避免的涉及到大量的數(shù)據(jù)存取過濾,ehcache支持內存+磁盤緩存不用擔心緩存容量問題,所以框架初步版本決定集成ehcache3,設計流程結構如下圖所示
緩存配置
maven引用
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency>
個性化配置
#緩存配置 cache: ehcache: heap: 1000 offheap: 100 disk: 500 diskDir: tempfiles/cache/
@Component @ConfigurationProperties("frmae.cache.ehcache") public class EhcacheConfiguration { ? ? /** ? ? ?* ehcache heap大小 ? ? ?* jvm內存中緩存的key數(shù)量 ? ? ?*/ ? ? private int heap; ? ? /** ? ? ?* ehcache offheap大小 ? ? ?* 堆外內存大小, 單位: MB ? ? ?*/ ? ? private int offheap; ? ? /** ? ? ?* 磁盤持久化目錄 ? ? ?*/ ? ? private String diskDir; ? ? /** ? ? ?* ehcache disk ? ? ?* 持久化到磁盤的大小, 單位: MB ? ? ?* diskDir有效時才生效 ? ? ?*/ ? ? private int disk; ? ? public EhcacheConfiguration(){ ? ? ? ? heap = 1000; ? ? ? ? offheap = 100; ? ? ? ? disk = 500; ? ? ? ? diskDir = "tempfiles/cache/"; ? ? } }
代碼注入配置
因為springboot默認緩存優(yōu)先注入redis配置,所以需要手動聲明bean進行注入,同時ehcache的value值必須支持序列化接口,不能使用Object代替,這里聲明一個緩存基類,所有緩存value對象必須繼承該類
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() ? ? ? ? ? ? ? ? // 堆內緩存大小 ? ? ? ? ? ? ? ? .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) ? ? ? ? ? ? ? ? //設置永不過期 ? ? ? ? ? ? ? ? .withExpiry(expiryPolicy) ? ? ? ? ? ? ? ? .build(); ? ? ? ? CacheManagerBuilder cacheManagerBuilder = CacheManagerBuilder.newCacheManagerBuilder() ? ? ? ? ? ? ? ? ?? ??? ? .with(CacheManagerBuilder.persistence(ehcacheConfiguration.getDiskDir())); ? ? ? ? return cacheManagerBuilder.build(true); ? ? } }
緩存操作
緩存預熱
針對緩存框架選擇的雙寫策略,即數(shù)據(jù)庫和緩存同時寫入,所以在系統(tǒng)啟動時需要預先將數(shù)據(jù)庫數(shù)據(jù)加載到緩存中
針對單表聲明自定義注解,個性化緩存定義自定義接口
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface HPCache { }
public interface IHPCacheInitService { ? ? String getCacheName(); ? ? void initCache(); }
系統(tǒng)初始化時同步進行緩存初始化,掃描注解實體類與接口實現(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配置類中需要進行緩存名稱的提前注冊,否則會導致操作緩存時空指針異常
? ? 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); ? ? ? ? }
更新操作
手動獲取ehcache的bean對象,調用put,repalce,delete方法進行操作
? ??? ?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); ? ? }
查詢操作
緩存存儲單表以主鍵—object形式存儲,個性化緩存為key-object形式存儲,單條記錄可以通過getCache方法查詢,列表查詢需要取出整個緩存按條件進行過濾
?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ù)庫數(shù)據(jù)一致性
數(shù)據(jù)庫數(shù)據(jù)操作與緩存操作順序為先操作數(shù)據(jù)后操作緩存,在開啟數(shù)據(jù)庫事務的情況下針對單條數(shù)據(jù)單次操作是沒有問題的,如果是組合操作一旦數(shù)據(jù)庫操作發(fā)生異?;貪L,緩存并沒有回滾就會導致數(shù)據(jù)的不一致,比如執(zhí)行順序為dbop1=》cacheop1=》dbop2=》cacheop2,dbop2異常,cacheop1的操作已經(jīng)更改了緩存
這里選擇的方案是在數(shù)據(jù)庫全部執(zhí)行完畢后統(tǒng)一操作緩存,這個方案有一個缺點是如果緩存操作發(fā)生異常還是會出現(xiàn)上述問題,實際過程中緩存只是對內存的操作異常概率較小,對緩存操作持樂觀狀態(tài),同時我們提供手動重置緩存的功能,算是一個折中方案,下面概述該方案的一個實現(xiàn)
聲明自定義緩存事務注解
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface CacheTransactional { }
聲明切面監(jiān)聽,在標記了CacheTransactional注解的方法執(zhí)行前進行Redis標識,統(tǒng)一執(zhí)行完方法體后執(zhí)行緩存操作
將緩存操作以線程id區(qū)分放入待執(zhí)行隊列中序列化到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()); ? ? }
到此這篇關于SpringBoot整合Ehcache3的實現(xiàn)步驟的文章就介紹到這了,更多相關SpringBoot整合Ehcache3內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
spring中FactoryBean中的getObject()方法實例解析
這篇文章主要介紹了spring中FactoryBean中的getObject()方法實例解析,分享了相關代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下2018-02-02詳解Java對象的強、軟、弱和虛引用+ReferenceQueue
這篇文章主要介紹了詳解Java對象的強、軟、弱和虛引用+ReferenceQueue的相關資料,需要的朋友可以參考下2017-06-06SpringBoot集成阿里巴巴Druid監(jiān)控的示例代碼
這篇文章主要介紹了SpringBoot集成阿里巴巴Druid監(jiān)控的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04Java 實戰(zhàn)范例之精美網(wǎng)上音樂平臺的實現(xiàn)
讀萬卷書不如行萬里路,只學書上的理論是遠遠不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+vue+Springboot+ssm+mysql+maven+redis實現(xiàn)一個前后端分離的精美網(wǎng)上音樂平臺,大家可以在過程中查缺補漏,提升水平2021-11-11mybatis?@InsertProvider報錯問題及解決
這篇文章主要介紹了mybatis?@InsertProvider報錯的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07