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

共同學習,共同進步,若有補充,歡迎指出,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM