<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SpringBoot整合Ehcache3

          共 10829字,需瀏覽 22分鐘

           ·

          2022-01-09 11:47

          前言

          公司部門(mén)老項(xiàng)目要遷移升級(jí)java版本,需要進(jìn)行緩存相關(guān)操作,原框架未支持這部分,經(jīng)過(guò)調(diào)研java相關(guān)緩存方案大致分為ehcache和redis兩種,redis的value最大值為500mb且超過(guò)1mb會(huì)對(duì)存取有性能影響,業(yè)務(wù)系統(tǒng)需要支持列表查詢(xún)緩存就不可避免的涉及到大量的數(shù)據(jù)存取過(guò)濾,ehcache支持內(nèi)存+磁盤(pán)緩存不用擔(dān)心緩存容量問(wèn)題,所以框架初步版本決定集成ehcache3,設(shè)計(jì)流程結(jié)構(gòu)如下圖所示

          緩存配置

          maven引用

                  <dependency>
          <groupId>org.springframework.bootgroupId>
          <artifactId>spring-boot-starter-cacheartifactId>
          dependency>
          <dependency>
          <groupId>org.ehcachegroupId>
          <artifactId>ehcacheartifactId>
          dependency>
          復(fù)制代碼

          個(gè)性化配置

            #緩存配置
          cache:
          ehcache:
          heap: 1000
          offheap: 100
          disk: 500
          diskDir: tempfiles/cache/
          復(fù)制代碼
          @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;
          /**
          * 磁盤(pán)持久化目錄
          */

          private String diskDir;
          /**
          * ehcache disk
          * 持久化到磁盤(pán)的大小, 單位: MB
          * diskDir有效時(shí)才生效
          */

          private int disk;

          public EhcacheConfiguration(){
          heap = 1000;
          offheap = 100;
          disk = 500;
          diskDir = "tempfiles/cache/";
          }
          }
          復(fù)制代碼

          代碼注入配置

          因?yàn)閟pringboot默認(rèn)緩存優(yōu)先注入redis配置,所以需要手動(dòng)聲明bean進(jìn)行注入,同時(shí)ehcache的value值必須支持序列化接口,不能使用Object代替,這里聲明一個(gè)緩存基類(lèi),所有緩存value對(duì)象必須繼承該類(lèi)

          public class BaseSystemObject implements Serializable {

          }
          復(fù)制代碼
          @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è)置永不過(guò)期
          .withExpiry(expiryPolicy)
          .build();

          CacheManagerBuilder cacheManagerBuilder = CacheManagerBuilder.newCacheManagerBuilder()
          .with(CacheManagerBuilder.persistence(ehcacheConfiguration.getDiskDir()));
          return cacheManagerBuilder.build(true);
          }
          }
          復(fù)制代碼

          緩存操作

          緩存預(yù)熱

          針對(duì)緩存框架選擇的雙寫(xiě)策略,即數(shù)據(jù)庫(kù)和緩存同時(shí)寫(xiě)入,所以在系統(tǒng)啟動(dòng)時(shí)需要預(yù)先將數(shù)據(jù)庫(kù)數(shù)據(jù)加載到緩存中

          針對(duì)單表聲明自定義注解,個(gè)性化緩存定義自定義接口

          @Target({ElementType.TYPE})
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          @Inherited
          public @interface HPCache {

          }

          復(fù)制代碼
          public interface IHPCacheInitService {

          String getCacheName();

          void initCache();
          }

          復(fù)制代碼

          系統(tǒng)初始化時(shí)同步進(jìn)行緩存初始化,掃描注解實(shí)體類(lèi)與接口實(shí)現(xiàn)Bean

          @Async
          public void initCache(Class runtimeClass, List extraPackageNameList) {
          List> 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> resultList = commonDTO.selectList(tableName.value(), "*", "1=1", "", new HashMap<>(), false);
          for (LinkedHashMap 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 res = context.getBeansOfType(IHPCacheInitService.class);
          for (Map.Entry en : res.entrySet()) {
          IHPCacheInitService service = (IHPCacheInitService) en.getValue();
          service.initCache();
          }

          System.out.println("緩存初始化完畢");
          }
          復(fù)制代碼

          需要注意,在EhcacheConfig配置類(lèi)中需要進(jìn)行緩存名稱(chēng)的提前注冊(cè),否則會(huì)導(dǎo)致操作緩存時(shí)空指針異常

              Map annotatedBeans = context.getBeansWithAnnotation(SpringBootApplication.class);
          Class runtimeClass = annotatedBeans.values().toArray()[0].getClass();
          //do,dao掃描
          List extraPackageNameList = new ArrayList();
          extraPackageNameList.add(Application.class.getPackage().getName());
          List> 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 res = context.getBeansOfType(IHPCacheInitService.class);
          for (Map.Entry en :res.entrySet()) {
          IHPCacheInitService service = (IHPCacheInitService)en.getValue();
          cacheManagerBuilder = cacheManagerBuilder.withCache(service.getCacheName(), config);
          }
          復(fù)制代碼

          更新操作

          手動(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);
          }
          復(fù)制代碼

          查詢(xún)操作

          緩存存儲(chǔ)單表以主鍵—object形式存儲(chǔ),個(gè)性化緩存為key-object形式存儲(chǔ),單條記錄可以通過(guò)getCache方法查詢(xún),列表查詢(xún)需要取出整個(gè)緩存按條件進(jìn)行過(guò)濾

           public Object getCache(String cacheName, String key){
          Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class);
          return cache.get(key);
          }

          public List 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;
          }
          復(fù)制代碼

          緩存與數(shù)據(jù)庫(kù)數(shù)據(jù)一致性

          數(shù)據(jù)庫(kù)數(shù)據(jù)操作與緩存操作順序?yàn)橄炔僮鲾?shù)據(jù)后操作緩存,在開(kāi)啟數(shù)據(jù)庫(kù)事務(wù)的情況下針對(duì)單條數(shù)據(jù)單次操作是沒(méi)有問(wèn)題的,如果是組合操作一旦數(shù)據(jù)庫(kù)操作發(fā)生異常回滾,緩存并沒(méi)有回滾就會(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)上述問(wèn)題,實(shí)際過(guò)程中緩存只是對(duì)內(nèi)存的操作異常概率較小,對(duì)緩存操作持樂(lè)觀狀態(tài),同時(shí)我們提供手動(dòng)重置緩存的功能,算是一個(gè)折中方案,下面概述該方案的一個(gè)實(shí)現(xiàn)

          聲明自定義緩存事務(wù)注解

          @Target({ElementType.METHOD})
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          @Inherited
          public @interface CacheTransactional {

          }
          復(fù)制代碼

          聲明切面監(jiān)聽(tīng),在標(biāo)記了CacheTransactional注解的方法執(zhí)行前進(jìn)行Redis標(biāo)識(shí),統(tǒng)一執(zhí)行完方法體后執(zhí)行緩存操作

          @Aspect
          @Component
          @Order(value = 101)
          public class CacheExecuteAspect {
          @Autowired
          private CacheExecuteUtil cacheExecuteUtil;


          /**
          * 切面點(diǎn) 指定注解
          */

          @Pointcut("@annotation(com.haopan.frame.common.annotation.CacheTransactional) " +
          "|| @within(com.haopan.frame.common.annotation.CacheTransactional)")

          public void cacheExecuteAspect() {

          }

          /**
          * 攔截方法指定為 repeatSubmitAspect
          */

          @Around("cacheExecuteAspect()")
          public Object around(ProceedingJoinPoint point) throws Throwable {
          MethodSignature signature = (MethodSignature) point.getSignature();
          Method method = signature.getMethod();
          CacheTransactional cacheTransactional = method.getAnnotation(CacheTransactional.class);
          if (cacheTransactional != null) {
          cacheExecuteUtil.putCacheIntoTransition();
          try{
          Object obj = point.proceed();
          cacheExecuteUtil.executeOperation();
          return obj;
          }catch (Exception e){
          e.printStackTrace();
          throw e;
          }
          } else {
          return point.proceed();
          }
          }
          }
          復(fù)制代碼

          將緩存操作以線程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;
          }
          復(fù)制代碼
          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 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());
          }


          作者:code2roc
          鏈接:https://juejin.cn/post/7048876487318962206
          來(lái)源:稀土掘金
          著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。



          瀏覽 48
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    国产成人无码免费看片 | www.日韩无码 | 国产和美国黄色毛片 | 国产成人三级在线播放 | 欧美成人高清 |