前面有写了一篇关于这个,但是这几天又改进了一点,就单独一篇在详细说明一下
配置 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
共同学习,共同进步,若有补充,欢迎指出,谢谢!