前面有寫了一篇關於這個,但是這幾天又改進了一點,就單獨一篇在詳細說明一下
配置 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
共同學習,共同進步,若有補充,歡迎指出,謝謝!