mybatis基于xml方式整合ehcache
上篇我们介绍了mybatis自带的二级缓存,但是这个缓存是单服务器工作,无法实现分布式缓存。那么什么是分布式缓存呢?假设现在有两个服务器1和2,用户访问的时候访问了1服务器,查询后的缓存就会放在1服务器上,假设现在有个用户访问的是2服务器,那么他在2服务器上就无法获取刚刚那个缓存,如下图所示:

(摘抄自 https://www.cnblogs.com/ysocean/p/7342498.html )
为了解决这个问题,就得找一个分布式的缓存,专门用来存储缓存数据的,这样不同的服务器要缓存数据都往它那里存,取缓存数据也从它那里取,如下图所示:

如上图所示,在几个不同的服务器之间,我们使用第三方缓存框架,将缓存都放在这个第三方框架中,然后无论有多少台服务器,我们都能从缓存中获取数据。
这里我们介绍mybatis与第三方框架ehcache的整合。
上文一开始提到过,mybatis提供了一个cache接口,如果要实现自己的缓存逻辑,实现cache接口开发即可。mybatis本身默认实现了一个,但是这个缓存的实现无法实现分布式缓存,所以我们要自己来实现。ehcache分布式缓存就可以,mybatis提供了一个针对cache接口的ehcache实现类,这个类在mybatis和ehcache的整合包中.
①、导入 mybatis-ehcache 整合包(最上面的源代码中包含有)

②、在全局配置文件 mybatis-configuration.xml 开启缓存
|
1
2
3
4
|
<!--开启二级缓存 --><settings> <setting name="cacheEnabled" value="true"/></settings> |
③、在 xxxMapper.xml 文件中整合 ehcache 缓存
将如下的类的全类名写入<cache type="" ></cache>的type属性中 (这里可以配置EhcacheCache 也可以配置 LoggingEhcache )
@Override public Object getObject(Object key) { requests++; final Object value = delegate.getObject(key); if (value != null) { hits++; } if (log.isDebugEnabled()) { log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio()); } return value; }
而从LoggingEhcache源码当中我们也看到,其不过是在debugEnabled的时候能打印缓存明中消息。其他功能跟Ehcache一样。
所以可以配置成如下 :
③、在 xxxMapper.xml 文件中整合 ehcache 缓存
将如下的类的全类名写入<cache type="" ></cache>的type属性中
|
1
2
3
4
5
|
<!-- 开启本mapper的namespace下的二级缓存 type:指定cache接口的实现类的类型,不写type属性,mybatis默认使用PerpetualCache 要和ehcache整合,需要配置type为ehcache实现cache接口的类型--><cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache> |
④、配置缓存参数
在 classpath 目录下新建一个 ehcache.xml 文件,并增加如下配置:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<?xml version="1.0" encoding="UTF-8"?><ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <diskStore path="F:\develop\ehcache"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" maxElementsOnDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <persistence strategy="localTempSwap"/> </defaultCache> </ehcache> |
diskStore:指定数据在磁盘中的存储位置。
defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略
以下属性是必须的:
maxElementsInMemory - 在内存中缓存的element的最大数目
maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
以下属性是可选的:
timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)。
额外:
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。
mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分的,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题可能需要在业务层根据需求对数据有针对性缓存。
下面附上我自己的源码:
首先,大家可以看我上篇的 https://www.cnblogs.com/gougouyangzi/articles/9982374.html mybatis基于xml方式整合二级缓存的代码。
然后需要作出改动的仅仅是 UserMapper.xml 当中的东西,如下所示
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="UserMapper">
<!-- 二级缓存+ ehcache 下面两个选一个就好,一个有日志,一个没有 -->
<!-- type="org.mybatis.caches.ehcache.EhcacheCache" -->
<cache type="org.mybatis.caches.ehcache.LoggingEhcache" />
<!--<cache></cache>-->
<select id="findUserById" parameterType="int" resultType="Map">
select user_id,user_name,CREATION_DATE from fnd_user where _id=#{id,jdbcType=INTEGER}
</select>
<!-- 测试能否执行存储过程 -->
<select id="callProcTest" parameterType="int" resultType="Map" statementType="CALLABLE">
{ call myfuncdictproc2(#{p_dict_id,mode=IN,jdbcType=INTEGER}) }
</select>
<!-- useCache="false" 是用来禁止二级缓存的 ,一定要设置成 true,这个时候才能使二级缓存、ehcache生效 -->
<select id="findUserByIdForCache" parameterType="int" resultType="Map" useCache="true">
select user_id,user_name,CREATION_DATE from fnd_user where _id=#{id,jdbcType=INTEGER}
</select>
</mapper>
注意到,一定要把cache在xml当中切换到 ehcache上,然后在对应的 select 当中 写 useCache=true。否则写成false的话,二级缓存、ehcache都是没有用的。
除此之外,我们引入了 pom的jar包,以及一个 ehcache.xml的配置.
pom的jar包截图如下 :

ehcache.xml如下 :
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true"> <defaultCache maxElementsInMemory="1" eternal="false" overflowToDisk="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800"> </defaultCache> <cache name="data-cache" maxElementsInMemory="500" overflowToDisk="false" eternal="true" timeToIdleSeconds="18000" timeToLiveSeconds="18000" memoryStoreEvictionPolicy="LRU" transactionalMode="off" /> </ehcache>
我们看下打印效果如下 :

和二级缓存简直如出一辙。
但是,等一下,还有个重要点没说,那就是我们现在在springboot使用,要让ehcache生效的话,必须使用
@EnableCaching 在启动类上表明。这样ehcache才表示生效了。
那怎么证明 我们的ehcache已经生效了呢? 我们可以在我们的main方法当中打印出 ehcache的相关信息 :

其次,我们最好的办法当然就是打端点罗 :
我们一步一步打端点,从mapperProxy -> MapperMethod -> DefaultSqlSession -> CachingExecutor

可以看出已经用到了 LoggingCache。一直到从Transaction 当中取出这个

得到了这个cache 并且key也是其组装形式的key。而且这个 transaction从本地取是取不到的:


从上图也看出了最终我们走的cache是Ehcache的来得到值的。

浙公网安备 33010602011771号