最近在做一个项目,为处理并发性较差的问题,使用了Mybatis二级缓存
但在多表联合查询的情况下,Mybatis二级缓存是存在着数据脏读的问题的
两天就是在想办法解决这个数据脏读的问题
考虑到简易性、性能、兼容性、可扩展性,我选择了springboot自带的 Ehcache 框架来整合解决这个问题
我也是第一次接触到 Ehcache 这个框架,所以也算从零开始
根据Mybatis二级缓存的特点,我的思路是
每当 增删改 某张表 , 就清除刷新其关联的所有表的 缓存
( 这里可分为两个大小方向:
在 增删改 的方法上,做相关的 关联清除缓存 操作 —— 这个就比较细节化一点,做的修改可能会比较多
在 增删改方法 所在类的整体上,做相关的 关联清除缓存操作 —— 这个我需要做的修改就相对少点,但这样里面每个方法(包括一些无关紧要的)都会触发这个清除缓存操作
都各有利弊 )
一路上并不顺利,
一开始我尝试自己看能不能找到比较好的方法,但刚接触一下子就自己解决也是不太可能的
但有看到一个解决Mybatis二级缓存问题的插件 —— GitHub上 LuanLouis 的 mybatis-enhanced-cache
但这个project是5年前写的,而且最近一次更新也只是2年前,现在技术发展那么快,看到的时候就感觉可能不行
后面自己也下载了下来,mvn install 发现不成功,缺少ojdbc14的jar包 ,网上说ojdbc14.jar 是收费的,所以中央库是导不了这个包
看了一些资料说,Oracle安装目录里面有,但公司这台没装Oracle ;也有资料说,可以用 ojdbc6 或其他ojdbc代替,我就下载了ojdbc6 和ojdbc8 都试了一下,都能够解决那个ojdbc jar包的问题,
mybatis-enhanced-cache 插件的jar也成功导入了maven库和项目中,但导入之后,用的时候却又发现报各种错,就觉得凉了,
本来就是为了简单快速,才想用别人的插件,所以我不可能自己又去修改这个插件来用,改了又不一定能用!所以使用插件这个方案就只能放弃了
后来,又找了网上很多资料,也按很多示例的步骤做了
像各种注解、各种配置什么的,都不知道是和mybatis的注解冲突了,还是我写的无效
(像网上很多资料说的,mybatis二级缓存是不推荐使用的,至于为什么我还要用,可以看我前面一篇文章—— 缓存的设计与使用 ,
所以在方面遇到我这样问题的应该没几个,也就找不到多少资料,只能靠自己)
想了很多可能性,试了一次又一次,想尽量做得简单点,直接简单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.WarnMapper" maxElementsInMemory="10000" eternal="false" 53 timeToIdleSeconds="120" timeToLiveSeconds="300" overflowToDisk="true" /> 54 55 <cache name="*.*.*.*.dao.ProjectMapper" maxElementsInMemory="10000" eternal="false"
56 timeToIdleSeconds="120" timeToLiveSeconds="300" overflowToDisk="true" />
57 </ehcache>
配置 application.properties
1 # Ehcache缓存 2 spring.cache.type=ehcache 3 spring.cache.ehcache.config=classpath:/ehcache.xml
业务逻辑层代码
1 package *.*.*.common.utils; 2 3 import net.sf.ehcache.Cache; 4 import net.sf.ehcache.CacheManager; 5 6 public class EhcacheUtil { 7 8 // 构建一个缓存管理器 9 private static CacheManager cacheManager = CacheManager.newInstance("src/main/resources/ehcache.xml"); 10 11 /** 12 * action 清除相关联的缓存 13 * @param cacheName 缓存所在namespace的名称 14 * @param keys 缓存所在namespace下key的名称,为空则默认清空所有key 15 * @return 16 */ 17 public static void clearRelatedCache( String cacheName, String[] keys ) { 18 Cache cache = cacheManager.getCache(cacheName) ; 19 if ( cache==null ) { 20 return ; 21 } 22 //若缓存不为空 23 if ( keys==null || keys.length==0 ) { 24 cache.removeAll(); 25 } 26 else { 27 for (String key : keys) { 28 cache.remove(key) ; 29 } 30 } 31 } 32 33 }
1 @PostMapping("/delete") 2 public Result delete(HttpServletRequest request) { 3 Long projectId = StringUtil.getLong(request.getParameter("id")) ; 4 Project project = projectService.findById(projectId) ; 5 if(project==null) { 6 return ResultGenerator.genFailResult(ResultCode.UNAUTHORIZED, "工程id不正确"); 7 } 8 projectService.deleteById(projectId);; 9 EhcacheUtil.clearRelatedCache(CacheConstants.CACHE_NAMESPACE_WARNMAPPER, null) ; 10 return ResultGenerator.genSuccessResult("删除成功"); 11 }
共同学习,共同进步,若有补充,欢迎指出,谢谢!