• Springboot整合Ehcache 解决Mybatis二级缓存数据脏读 -详细


    前面有写了一篇关于这个,但是这几天又改进了一点,就单独一篇在详细说明一下

    配置 application.properties ,启用Ehcache

    1 # Ehcache缓存
    2 spring.cache.type=ehcache
    3 spring.cache.ehcache.config=classpath:/ehcache.xml

    配置 ehcache.xml ,设置缓存相关属性

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <!-- <ehcache> -->
     3 <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     4          xsi:noNamespaceSchemaLocation="ehcache.xsd">
     5 
     6     <!--
     7         磁盘存储:将缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存
     8         path:指定在硬盘上存储对象的路径
     9         path可以配置的目录有:
    10             user.home(用户的家目录)
    11             user.dir(用户当前的工作目录)
    12             java.io.tmpdir(默认的临时目录)
    13             ehcache.disk.store.dir(ehcache的配置目录)
    14             绝对路径(如:d:\ehcache)
    15         查看路径方法:String tmpDir = System.getProperty("java.io.tmpdir");
    16      -->
    17     <diskStore path="java.io.tmpdir" />
    18 
    19     <!-- 配置提供者 1、peerDiscovery,提供者方式,有两种方式:自动发现(automatic)、手动配置(manual) 2、rmiUrls,手动方式时提供者的地址,多个的话用|隔开 -->
    20     <!-- <cacheManagerPeerProviderFactory
    21         class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
    22         properties="peerDiscovery=manual,rmiUrls=//127.0.0.1:40002/userCache" /> -->
    23     <cacheManagerPeerProviderFactory
    24         class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
    25         properties="peerDiscovery=automatic, multicastGroupAddress=230.0.0.1, multicastGroupPort=4446,timeToLive=255"/>
    26     <!-- <cacheManagerPeerProviderFactory
    27         class="org.ehcache.distribution.RMICacheManagerPeerProviderFactory"
    28         properties="peerDiscovery=automatic, multicastGroupAddress=230.0.0.1, multicastGroupPort=4446,timeToLive=255"/> -->
    29 
    30     <!-- 配置监听器 1、hostName 主机地址 2、port 端口 3、socketTimeoutMillis socket子模块的超时时间,默认是2000ms -->
    31     <!-- <cacheManagerPeerListenerFactory
    32         class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
    33         properties="hostName=127.0.0.1, port=40001, socketTimeoutMillis=2000" /> -->
    34     <cacheManagerPeerListenerFactory
    35          class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"/>
    36 
    37 
    38     <!--
    39         defaultCache:默认的缓存配置信息,如果不加特殊说明,则所有对象按照此配置项处理
    40         maxElementsInMemory:设置了缓存的上限,最多存储多少个记录对象
    41         eternal:代表对象是否永不过期 (指定true则下面两项配置需为0无限期)
    42         timeToIdleSeconds:最大的闲置时间 /秒
    43         timeToLiveSeconds:最大的存活时间 /秒
    44         overflowToDisk:是否允许对象被写入到磁盘
    45         说明:下列配置自缓存建立起600秒(10分钟)有效 。
    46         在有效的600秒(10分钟)内,如果连续120秒(2分钟)未访问缓存,则缓存失效。
    47         就算有访问,也只会存活600秒。
    48      -->
    49     <defaultCache maxElementsInMemory="10000" eternal="false"
    50                   timeToIdleSeconds="600" timeToLiveSeconds="600" overflowToDisk="true" />
    51 
    52     <cache name="*.*.*.*.dao.DeviceMapper" maxElementsInMemory="10000" eternal="false"
    53                   timeToIdleSeconds="120" overflowToDisk="true" />
    54 
    55     <cache name="*.*.*.*.dao.ProjectMapper" maxElementsInMemory="10000" eternal="false"
    56                   timeToIdleSeconds="120" overflowToDisk="true" />
    57                   
    58     <cache name="*.*.*.*.dao.WarnMapper" maxElementsInMemory="10000" eternal="false"
    59                   timeToIdleSeconds="120" timeToLiveSeconds="300" overflowToDisk="true" />
    60      
    61 </ehcache>

    配置 cache-dependencies.xml ,指定 各namespace缓存之间的依赖关联

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <dependencies>
     3 
     4     <relations>
     5         <!-- 通过key的值 与 调用类的类名 进行匹配,从而确定当前是在清除或刷新那个namespace下的缓存 -->
     6         <relation key="Project">
     7             <cacheNamespace>*.*.*.*.dao.ProjectMapper</cacheNamespace>
     8         </relation>
     9         <relation key="Device">
    10             <cacheNamespace>*.*.*.*.dao.DeviceMapper</cacheNamespace>
    11         </relation>
    12         <relation key="Warn">
    13             <cacheNamespace>*.*.*.*.dao.WarnMapper</cacheNamespace>
    14         </relation>
    15     </relations>
    16     
    17     <statements>
    18         <!-- 
    19             id 为缓存的namespace
    20             uni-directional 表示单向关联,即statement的缓存刷新会清除observer的缓存,而observer的缓存的刷新不会清除statement的缓存
    21             一个statement标签下可有多个observer标签
    22          -->
    23         <statement id="*.*.*.*.dao.ProjectMapper" type="uni-directional">
    24             <observer id="*.*.*.*.dao.WarnMapper" />
    25         </statement>
    26         <statement id="*.*.*.*.dao.DeviceMapper" type="uni-directional">
    27             <observer id="*.*.*.*.dao.WarnMapper" />
    28         </statement>
    29         
    30         <!-- 
    31             id 为缓存的namespace
    32             bi-directional 表示双向关联,statement和observer的缓存刷新都会对双方造成影响
    33             一个statement标签下可有多个observer标签
    34          -->
    35         <!-- <statement id="*.*.*.*.dao.DeviceMapper" type="bi-directional">
    36             <observer id="*.*.*.*.dao.WarnMapper" />
    37         </statement> -->
    38         
    39         <!-- notice: 如果 双向关联 和 单向关联 的内容一样,则以双向的规则为准 -->
    40     </statements>
    41 
    42 </dependencies>
    编写常量类 CacheConstants
     1 package *.*.*.*.constants;
     2 
     3 public class CacheConstants {
     4 
     5     /**
     6      * ehcache_config
     7      */
     8 //    public static final String  EHCACHE_CONFIG = "src/main/resources/ehcache.xml";
     9     public static final String  EHCACHE_CONFIG = "ehcache.xml";
    10     
    11     /**
    12      * cache_dependencies
    13      */
    14 //    public static final String  CACHE_DEPENDENCIES = "src/main/resources/cache-dependencies.xml";
    15     public static final String  CACHE_DEPENDENCIES = "cache-dependencies.xml";
    16 
    17     
    18 }

    编写 EhcacheUtil 类 

      1 package *.*.*.*.utils;
      2 
      3 import java.io.File;
      4 import java.util.ArrayList;
      5 import java.util.List;
      6 import java.util.Map;
      7 import java.util.concurrent.ConcurrentHashMap;
      8 
      9 import org.dom4j.Document;
     10 import org.dom4j.DocumentException;
     11 import org.dom4j.Element;
     12 import org.dom4j.io.SAXReader;
     13 import org.slf4j.Logger;
     14 import org.slf4j.LoggerFactory;
     15 
     16 import *.*.*.*.constants.CacheConstants;
     17 
     18 import net.sf.ehcache.Cache;
     19 import net.sf.ehcache.CacheManager;
     20 
     21 public class EhcacheUtil {
     22 
     23     private static final Logger logger = LoggerFactory.getLogger(EhcacheUtil.class);
     24     // 构建一个缓存管理器、单例对象
     25 //    private static CacheManager cacheManager = CacheManager.newInstance("src/main/resources/ehcache.xml");  
     26 //    private static CacheManager cacheManager = CacheManager.newInstance(CacheConstants.EHCACHE_CONFIG);  
    private static CacheManager cacheManager = null ;
    27 // 加载缓存依赖关系的XML 28 // private static String cache_dependencies_xml_path = "src/main/resources/cache-dependencies.xml" ; 29 private static String cache_dependencies_xml_path = CacheConstants.CACHE_DEPENDENCIES ; 30 // 前面一个调用类的类名 31 private static String className = new Exception().getStackTrace()[1].getClassName() ; 32 private static String CLASS_RELATE_CACHE_MAP = "classRelateCacheMap" ; 33 private static String CACHE_RELATE_CACHE_MAP = "cacheRelateCacheMap" ; 34

    static{
       cacheManager= CacheManager.newInstance(EhcacheUtil.class.getClassLoader().getResourceAsStream(CacheConstants.EHCACHE_CONFIG));
    }

     35     /**
     36      * action 根据调用类的类名,清除相关联的缓存
     37      * @return 
     38      */
     39     @SuppressWarnings("unchecked")
     40     public static void clearRelatedCache(String className) {
     41 //        System.out.println("className: "+className);
     42         Map<String, Object> map = parseXml() ;
     43         Map<String, String> classRelateCacheMap = (Map<String, String>) map.get(CLASS_RELATE_CACHE_MAP) ;
     44         Map<String, List<String>> cacheRelateCacheMap = (Map<String, List<String>>) map.get(CACHE_RELATE_CACHE_MAP) ;
     45         String sourceCacheName = null ;
     46         for (String key : classRelateCacheMap.keySet()) {
     47             if ( className.contains(key) ) {
     48                 sourceCacheName = (String) classRelateCacheMap.get(key) ;
     49             }
     50         }
     51         if ( sourceCacheName==null ) {
     52             return ;
     53         }
     54         List<String> destCacheNames = new ArrayList<String>() ;
     55         for (String key : cacheRelateCacheMap.keySet()) {
     56             if ( key.equals(sourceCacheName) ) {
     57                 destCacheNames = cacheRelateCacheMap.get(key) ;
     58             }
     59         }
     60         for (String cacheNameNeedClear : destCacheNames) {
     61             clearRelatedCache(cacheNameNeedClear, null);
     62         }        
     63     }
     64     
     65     /**
     66      * action 自动识别调用类的类名,清除相关联的缓存
     67      * @return 
     68      */
     69     @SuppressWarnings("unchecked")
     70     public static void clearRelatedCache() {
     71 //        System.out.println("className: "+className);
     72         Map<String, Object> map = parseXml() ;
     73 //        System.out.println( map.toString() );
     74         Map<String, String> classRelateCacheMap = (Map<String, String>) map.get(CLASS_RELATE_CACHE_MAP) ;
     75         Map<String, List<String>> cacheRelateCacheMap = (Map<String, List<String>>) map.get(CACHE_RELATE_CACHE_MAP) ;
     76         String sourceCacheName = null ;
     77         for (String key : classRelateCacheMap.keySet()) {
     78             if ( className.contains(key) ) {
     79                 sourceCacheName = (String) classRelateCacheMap.get(key) ;
     80 //                System.out.println("sourceCacheName: "+sourceCacheName);
     81             }
     82         }
     83         if ( sourceCacheName==null ) {
     84             return ;
     85         }
     86         List<String> destCacheNames = new ArrayList<String>() ;
     87         for (String key : cacheRelateCacheMap.keySet()) {
     88             if ( key.equals(sourceCacheName) ) {
     89                 destCacheNames = cacheRelateCacheMap.get(key) ;
     90             }
     91         }
     92         for (String cacheNameNeedClear : destCacheNames) {
     93 //            System.out.println("cacheNameNeedClear: "+cacheNameNeedClear);
     94             clearRelatedCache(cacheNameNeedClear, null);
     95         }        
     96     }
     97     
     98     /**
     99      * action 清除相关联的缓存
    100      * @param cacheName 缓存所在namespace的名称
    101      * @param keys   缓存所在namespace下key的名称,为空则默认清空所有key
    102      * @return 
    103      */
    104     public static void clearRelatedCache( String cacheName, String[] keys ) {
    105         Cache cache = cacheManager.getCache(cacheName) ;    
    106         if ( cache==null ) { 
    107             return ;
    108         }
    109         //若缓存不为空
    110         if ( keys==null || keys.length==0 ) {
    111             cache.removeAll();
    112         }
    113         else {
    114             for (String key : keys) {
    115                 cache.remove(key) ;
    116             }            
    117         }
    118 //        String[] cacheNames = cacheManager.getCacheNames() ;
    119 //        System.out.println(Arrays.asList(cacheNames));
    120     }
    121 
    122     public static Map<String, Object> parseXml() {
    123         Map<String, String> classRelateCacheMap = new ConcurrentHashMap<String,String>() ;
    124         Map<String, List<String>> cacheRelateCacheMap = new ConcurrentHashMap<String,List<String>>() ;
    125         Map<String, Object> map = new ConcurrentHashMap<String,Object>() ;
    126         SAXReader saxReader = new SAXReader() ;
    127         Document document = null ;
    128         try {
    129 //            File file = new File(cache_dependencies_xml_path) ;
    130 //            document = saxReader.read(file);

    document = saxReader.read(EhcacheUtil.class.getClassLoader().getResourceAsStream(CacheConstants.CACHE_DEPENDENCIES));

    131             Element rootEl = document.getRootElement() ;
    132             readElementAll(rootEl, classRelateCacheMap, cacheRelateCacheMap) ;
    133         }
    134         catch (DocumentException e) {
    135             logger.warn(e.getMessage()) ;
    136         } catch (Exception e) {
    137             logger.warn(e.getMessage()) ;
    138         }
    139         finally {
    140             map.put(CLASS_RELATE_CACHE_MAP, classRelateCacheMap) ;
    141             map.put(CACHE_RELATE_CACHE_MAP, cacheRelateCacheMap) ;
    142         }
    143         return map ;
    144     }
    145 
    146     /**
    147      * XML转Map 从根节点开始,逐层递归遍历所有子节点
    148      */
    149     @SuppressWarnings("unchecked")
    150     public static void readElementAll(Element node, Map<String, String> classRelateCacheMap, Map<String, List<String>> cacheRelateCacheMap) {
    151         // 当前节点的名称、文本内容和属性
    152 //        System.out.println("当前节点的名称:"+node.getName());//当前节点名称
    153 //        System.out.println("当前节点的内容:"+node.getText());//当前节点的值
    154         // 所有一级子节点的list
    155         List<Element> listElement = node.elements();
    156         // 逐级遍历所有子节点
    157         for (Element e : listElement) {
    158             if (e.getName().equals("relation")) {
    159                 List<Element> listCacheNamespace = e.elements() ;
    160                 for (Element eCacheNamespace : listCacheNamespace) {
    161                     classRelateCacheMap.put(e.attributeValue("key"), eCacheNamespace.getText()) ;
    162                     // 取到value后,要把这个节点删掉,不然在下一级会被再处理一遍
    163                     e.remove(eCacheNamespace);
    164                 }
    165             } else if ( e.getName().equals("statement") ) {
    166                 String type = e.attributeValue("type") ;
    167                 List<Element> listObserver = e.elements();
    168                 List<String> list = new ArrayList<String>() ;
    169                 for (Element eObserver : listObserver) {
    170                     list.add( eObserver.attributeValue("id") ) ;
    171                     if ( type.equals("bi-directional") ) {
    172                         List<String> olist = cacheRelateCacheMap.get( eObserver.attributeValue("id") ) ;
    173                         olist.add( e.attributeValue("id") ) ;
    174                         cacheRelateCacheMap.put(eObserver.attributeValue("id"), olist) ;
    175                     }
    176                     e.remove(eObserver);
    177                 }
    178                 cacheRelateCacheMap.put(e.attributeValue("id"), list) ;
    179             } 
    180             readElementAll(e, classRelateCacheMap, cacheRelateCacheMap) ;
    181         }
    182     }
    183     
    184 }

    在 BaseService<T> 通用service类 进行调用

     1 /**
     2  * 基于通用MyBatis Mapper插件的Service接口的实现
     3  */
     4 public abstract class BaseService<T> implements Service<T> {
     5 
     6     @Autowired
     7     protected Mapper<T> mapper;
     8 
     9     private Class<T> modelClass;    // 当前泛型真实类型的Class
    10 
    11     public BaseService() {
    12         ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
    13         modelClass = (Class<T>) pt.getActualTypeArguments()[0];
    14     }
    15 
    16     public void save(T model) {
    17         mapper.insertSelective(model);
    18         EhcacheUtil.clearRelatedCache(modelClass.getName());
    19     }
    20 
    21     public void save(List<T> models) {
    22         mapper.insertList(models);
    23         EhcacheUtil.clearRelatedCache(modelClass.getName());
    24     }
    25 
    26     public void deleteById(Integer id) {
    27         mapper.deleteByPrimaryKey(id);
    28         EhcacheUtil.clearRelatedCache(modelClass.getName());
    29     }

    至此,解决了Mybatis二级缓存数据脏读问题

    190805 - v2

    共同学习,共同进步,若有补充,欢迎指出,谢谢!

  • 相关阅读:
    去除Html标签
    asp.net弹出多个模态窗口
    window.returnValue的用法
    eTerm-用于报价的指令(GK状态码的使用)
    使用ffmpeg 操作音频文件前后部分静音移除.
    使用Visual Studio 2017开发python,并在iis上部署Python Django
    解决wampserver 服务无法启动
    网站优化记录-通过命令预编译Asp.net 网站,成功优化到毫秒级别。
    Scut游戏引擎改造兼容Codis。
    windows修改Host后未生效。
  • 原文地址:https://www.cnblogs.com/dengguangxue/p/11286688.html
Copyright © 2020-2023  润新知