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

配置 application.properties ,启用Ehcache

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

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

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

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

 <?xml version="1.0" encoding="UTF-8"?>
<dependencies> <relations>
<!-- 通过key的值 与 调用类的类名 进行匹配,从而确定当前是在清除或刷新那个namespace下的缓存 -->
<relation key="Project">
<cacheNamespace>*.*.*.*.dao.ProjectMapper</cacheNamespace>
</relation>
<relation key="Device">
<cacheNamespace>*.*.*.*.dao.DeviceMapper</cacheNamespace>
</relation>
<relation key="Warn">
<cacheNamespace>*.*.*.*.dao.WarnMapper</cacheNamespace>
</relation>
</relations> <statements>
<!--
id 为缓存的namespace
uni-directional 表示单向关联,即statement的缓存刷新会清除observer的缓存,而observer的缓存的刷新不会清除statement的缓存
一个statement标签下可有多个observer标签
-->
<statement id="*.*.*.*.dao.ProjectMapper" type="uni-directional">
<observer id="*.*.*.*.dao.WarnMapper" />
</statement>
<statement id="*.*.*.*.dao.DeviceMapper" type="uni-directional">
<observer id="*.*.*.*.dao.WarnMapper" />
</statement> <!--
id 为缓存的namespace
bi-directional 表示双向关联,statement和observer的缓存刷新都会对双方造成影响
一个statement标签下可有多个observer标签
-->
<!-- <statement id="*.*.*.*.dao.DeviceMapper" type="bi-directional">
<observer id="*.*.*.*.dao.WarnMapper" />
</statement> --> <!-- notice: 如果 双向关联 和 单向关联 的内容一样,则以双向的规则为准 -->
</statements> </dependencies>
编写常量类 CacheConstants
 package *.*.*.*.constants;

 public class CacheConstants {

     /**
* ehcache_config
*/
// public static final String EHCACHE_CONFIG = "src/main/resources/ehcache.xml";
public static final String EHCACHE_CONFIG = "ehcache.xml"; /**
* cache_dependencies
*/
// public static final String CACHE_DEPENDENCIES = "src/main/resources/cache-dependencies.xml";
public static final String CACHE_DEPENDENCIES = "cache-dependencies.xml"; }

编写 EhcacheUtil 类

 package *.*.*.*.utils;

 import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import *.*.*.*.constants.CacheConstants; import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager; public class EhcacheUtil { private static final Logger logger = LoggerFactory.getLogger(EhcacheUtil.class);
// 构建一个缓存管理器、单例对象
// private static CacheManager cacheManager = CacheManager.newInstance("src/main/resources/ehcache.xml");
// private static CacheManager cacheManager = CacheManager.newInstance(CacheConstants.EHCACHE_CONFIG);
private static CacheManager cacheManager = null ;
// 加载缓存依赖关系的XML
// private static String cache_dependencies_xml_path = "src/main/resources/cache-dependencies.xml" ;
private static String cache_dependencies_xml_path = CacheConstants.CACHE_DEPENDENCIES ;
// 前面一个调用类的类名
private static String className = new Exception().getStackTrace()[1].getClassName() ;
private static String CLASS_RELATE_CACHE_MAP = "classRelateCacheMap" ;
private static String CACHE_RELATE_CACHE_MAP = "cacheRelateCacheMap" ;

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

     /**
* action 根据调用类的类名,清除相关联的缓存
* @return
*/
@SuppressWarnings("unchecked")
public static void clearRelatedCache(String className) {
// System.out.println("className: "+className);
Map<String, Object> map = parseXml() ;
Map<String, String> classRelateCacheMap = (Map<String, String>) map.get(CLASS_RELATE_CACHE_MAP) ;
Map<String, List<String>> cacheRelateCacheMap = (Map<String, List<String>>) map.get(CACHE_RELATE_CACHE_MAP) ;
String sourceCacheName = null ;
for (String key : classRelateCacheMap.keySet()) {
if ( className.contains(key) ) {
sourceCacheName = (String) classRelateCacheMap.get(key) ;
}
}
if ( sourceCacheName==null ) {
return ;
}
List<String> destCacheNames = new ArrayList<String>() ;
for (String key : cacheRelateCacheMap.keySet()) {
if ( key.equals(sourceCacheName) ) {
destCacheNames = cacheRelateCacheMap.get(key) ;
}
}
for (String cacheNameNeedClear : destCacheNames) {
clearRelatedCache(cacheNameNeedClear, null);
}
} /**
* action 自动识别调用类的类名,清除相关联的缓存
* @return
*/
@SuppressWarnings("unchecked")
public static void clearRelatedCache() {
// System.out.println("className: "+className);
Map<String, Object> map = parseXml() ;
// System.out.println( map.toString() );
Map<String, String> classRelateCacheMap = (Map<String, String>) map.get(CLASS_RELATE_CACHE_MAP) ;
Map<String, List<String>> cacheRelateCacheMap = (Map<String, List<String>>) map.get(CACHE_RELATE_CACHE_MAP) ;
String sourceCacheName = null ;
for (String key : classRelateCacheMap.keySet()) {
if ( className.contains(key) ) {
sourceCacheName = (String) classRelateCacheMap.get(key) ;
// System.out.println("sourceCacheName: "+sourceCacheName);
}
}
if ( sourceCacheName==null ) {
return ;
}
List<String> destCacheNames = new ArrayList<String>() ;
for (String key : cacheRelateCacheMap.keySet()) {
if ( key.equals(sourceCacheName) ) {
destCacheNames = cacheRelateCacheMap.get(key) ;
}
}
for (String cacheNameNeedClear : destCacheNames) {
// System.out.println("cacheNameNeedClear: "+cacheNameNeedClear);
clearRelatedCache(cacheNameNeedClear, null);
}
} /**
* action 清除相关联的缓存
* @param cacheName 缓存所在namespace的名称
* @param keys 缓存所在namespace下key的名称,为空则默认清空所有key
* @return
*/
public static void clearRelatedCache( String cacheName, String[] keys ) {
Cache cache = cacheManager.getCache(cacheName) ;
if ( cache==null ) {
return ;
}
//若缓存不为空
if ( keys==null || keys.length==0 ) {
cache.removeAll();
}
else {
for (String key : keys) {
cache.remove(key) ;
}
}
// String[] cacheNames = cacheManager.getCacheNames() ;
// System.out.println(Arrays.asList(cacheNames));
} public static Map<String, Object> parseXml() {
Map<String, String> classRelateCacheMap = new ConcurrentHashMap<String,String>() ;
Map<String, List<String>> cacheRelateCacheMap = new ConcurrentHashMap<String,List<String>>() ;
Map<String, Object> map = new ConcurrentHashMap<String,Object>() ;
SAXReader saxReader = new SAXReader() ;
Document document = null ;
try {
// File file = new File(cache_dependencies_xml_path) ;
// document = saxReader.read(file);

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

             Element rootEl = document.getRootElement() ;
readElementAll(rootEl, classRelateCacheMap, cacheRelateCacheMap) ;
}
catch (DocumentException e) {
logger.warn(e.getMessage()) ;
} catch (Exception e) {
logger.warn(e.getMessage()) ;
}
finally {
map.put(CLASS_RELATE_CACHE_MAP, classRelateCacheMap) ;
map.put(CACHE_RELATE_CACHE_MAP, cacheRelateCacheMap) ;
}
return map ;
} /**
* XML转Map 从根节点开始,逐层递归遍历所有子节点
*/
@SuppressWarnings("unchecked")
public static void readElementAll(Element node, Map<String, String> classRelateCacheMap, Map<String, List<String>> cacheRelateCacheMap) {
// 当前节点的名称、文本内容和属性
// System.out.println("当前节点的名称:"+node.getName());//当前节点名称
// System.out.println("当前节点的内容:"+node.getText());//当前节点的值
// 所有一级子节点的list
List<Element> listElement = node.elements();
// 逐级遍历所有子节点
for (Element e : listElement) {
if (e.getName().equals("relation")) {
List<Element> listCacheNamespace = e.elements() ;
for (Element eCacheNamespace : listCacheNamespace) {
classRelateCacheMap.put(e.attributeValue("key"), eCacheNamespace.getText()) ;
// 取到value后,要把这个节点删掉,不然在下一级会被再处理一遍
e.remove(eCacheNamespace);
}
} else if ( e.getName().equals("statement") ) {
String type = e.attributeValue("type") ;
List<Element> listObserver = e.elements();
List<String> list = new ArrayList<String>() ;
for (Element eObserver : listObserver) {
list.add( eObserver.attributeValue("id") ) ;
if ( type.equals("bi-directional") ) {
List<String> olist = cacheRelateCacheMap.get( eObserver.attributeValue("id") ) ;
olist.add( e.attributeValue("id") ) ;
cacheRelateCacheMap.put(eObserver.attributeValue("id"), olist) ;
}
e.remove(eObserver);
}
cacheRelateCacheMap.put(e.attributeValue("id"), list) ;
}
readElementAll(e, classRelateCacheMap, cacheRelateCacheMap) ;
}
} }

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

 /**
* 基于通用MyBatis Mapper插件的Service接口的实现
*/
public abstract class BaseService<T> implements Service<T> { @Autowired
protected Mapper<T> mapper; private Class<T> modelClass; // 当前泛型真实类型的Class public BaseService() {
ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
modelClass = (Class<T>) pt.getActualTypeArguments()[0];
} public void save(T model) {
mapper.insertSelective(model);
EhcacheUtil.clearRelatedCache(modelClass.getName());
} public void save(List<T> models) {
mapper.insertList(models);
EhcacheUtil.clearRelatedCache(modelClass.getName());
} public void deleteById(Integer id) {
mapper.deleteByPrimaryKey(id);
EhcacheUtil.clearRelatedCache(modelClass.getName());
}

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

190805 - v2

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

Springboot整合Ehcache 解决Mybatis二级缓存数据脏读 -详细的更多相关文章

  1. mybatis二级缓存应用及与ehcache整合

    mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存. 1.开启mybatis的二级缓存 在核心配 ...

  2. SpringBoot整合Redis、mybatis实战,封装RedisUtils工具类,redis缓存mybatis数据 附源码

    创建SpringBoot项目 在线创建方式 网址:https://start.spring.io/ 然后创建Controller.Mapper.Service包 SpringBoot整合Redis 引 ...

  3. 深入了解MyBatis二级缓存

    深入了解MyBatis二级缓存 标签: mybatis二级缓存 2015-03-30 08:57 41446人阅读 评论(13) 收藏 举报  分类: Mybatis(51)  版权声明:版权归博主所 ...

  4. MyBatis二级缓存配置

    正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持 Mybatis二级缓存是SessionFactory,如果两次查询基于同一个SessionFactory,那么就从二级缓存 ...

  5. MyBatis 二级缓存全详解

    目录 MyBatis 二级缓存介绍 二级缓存开启条件 探究二级缓存 二级缓存失效的条件 第一次SqlSession 未提交 更新对二级缓存影响 探究多表操作对二级缓存的影响 二级缓存源码解析 二级缓存 ...

  6. 【Springboot】Springboot整合Ehcache

    刚刚项目上线了,记录下使用的技术...... EhCache 是一个纯Java的进程内缓存框架,具有快速.精干等特点,是Hibernate中默认的CacheProvider. Ehcache的特点 ( ...

  7. Mybatis 二级缓存应用 (21)

    [MyBatis 二级缓存] 概述:一级缓存作用域为同一个SqlSession对象,而二级缓存用来解决一级缓存不能夸会话共享,作用范围是namespace级,可以被多个SqlSession共享(只要是 ...

  8. mybatis二级缓存

    二级缓存区域是根据mapper的namespace划分的,相同namespace的mapper查询数据放在同一个区域,如果使用mapper代理方法每个mapper的namespace都不同,此时可以理 ...

  9. 如何细粒度地控制你的MyBatis二级缓存(mybatis-enhanced-cache插件实现)

    前几天网友chanfish 给我抛出了一个问题,笼统地讲就是如何能细粒度地控制MyBatis的二级缓存问题,酝酿了几天,觉得可以写个插件来实现这个这一功能.本文就是从问题入手,一步步分析现存的MyBa ...

随机推荐

  1. highway network及mnist数据集测试

    先说结论:没经过仔细调参,打不开论文所说代码链接(fq也没打开),结果和普通卷积网络比较没有优势.反倒是BN对网络起着非常重要的作用,达到了99.17%的测试精度(训练轮数还没到过拟合). 论文为&l ...

  2. xss 学习(一)

    存储型 .反射型.DOM 型这是最常见的三种分类: 存储型存储型XSS也叫持久型XSS,存储的意思就是Payload是有经过存储的,当一个页面存在存储型XSS的时候,XSS注入成功后,那么每次访问该页 ...

  3. shell-常用命令,重定向和文件包含

    shell的知识点并不多,这里简单介绍一下常用的一些东西 常用命令 echo 显示普通字符串 echo "test" 显示转义字符 echo "\"test\& ...

  4. SQL -------- JDBC 修改某条记录得内容

    package demo; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; ...

  5. beam search 和 greedy search

    贪心搜索(greedy search): 贪心搜索最为简单,直接选择每个输出的最大概率,直到出现终结符或最大句子长度. 集束搜索(beam search): 集束搜索可以认为是维特比算法的贪心形式,在 ...

  6. 关于keildownload键变为灰色不能使用的问题

    有时候  有些朋友可能会遇到这样的问题 突然间download键变成灰色,导致不能将程序下载到芯片中 遇到中问题如果不是软件卡了  那就是可能一不小心点错了,关掉了下载条件“约定” 可以点“魔术棒” ...

  7. arm-linux的gdb移植

    转载于:http://blog.chinaunix.net/uid-23381466-id-309369.html arm-linux的gdb移植分为两种情况.一种是交叉调试版.这一种模式是需要编译一 ...

  8. DOS cscript

    C:\>cscript /?Microsoft (R) Windows Script Host Version 5.812版权所有(C) Microsoft Corporation.保留所有权利 ...

  9. hdu 4471 区间条件统计 区间 不超过 x 的元素的个数

    题目传送门//res tp hdu 目的 对长度为n的区间,m次询问,每次提供一个区间两端点与一个值x,求区间内不超过x的元素个数 n 1e5 m 1e5 ai [1,1e9] (i∈[1,n]) 多 ...

  10. H5传奇源码,附带微信支付,商城系统,新增了元宝交易商城系统源码

    源码说明:传奇游戏是80年底的经典游戏,传奇源码,H5游戏源码下载,附带微信支付,商城系统,新增了元宝交易商城系统源码,内置很多任务,比如首冲任务,修复了很多BUG.[架设要求]游戏名称:H5传奇世界 ...