1 Ehcache
1.1 pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- ehcache坐标 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
1.2 ehcache.xml
1.2.1 示例
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<diskStore path="java.io.tmpdir"/>
<!--defaultCache:echcache 的默认缓存策略 -->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
<!-- 自定义缓存策略 -->
<cache name="users"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</cache>
</ehcache>
1.2.2 参数说明
文件中参数说明:
-
diskStore
:为缓存路径,ehcache
分为内存
和磁盘
两级,此属性定义磁盘的缓存位置。参数解释如下:-
user.home
:用户主目录 -
user.dir
:用户当前工作目录 -
java.io.tmpdir
:默认临时文件路径
-
-
defaultCache
和cache
都是用来指定缓存的,defaultCache
只能有一个,如果在使用时,没有指定具体缓存名字就用这个defaultCache
默认缓存,cache
缓存可以有多个,但是里面的name
必须是不同名字 -
cache
标签-
name
:缓存名称 -
maxElementsInMemory
:缓存最大数目 -
maxElementsOnDisk
:硬盘最大缓存个数。 -
eternal
:对象是否永久有效,一但设置了,timeout
将不起作用。 -
overflowToDisk
:是否保存到磁盘,当系统宕机时保存磁盘 -
timeToIdleSeconds
:设置对象在失效前的允许闲置时间(单位:秒
)。仅当eternal=false
对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 -
timeToLiveSeconds
:设置对象在失效前允许存活时间(单位:秒
)。最大时间介于创建时间
和失效时间
之间。仅当eternal=false对
象不是永久有效时使用,默认是0,也就是对象存活时间无穷大。 -
diskPersistent
:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. -
diskSpoolBufferSizeMB
:这个参数设置DiskStore
(磁盘缓存)的缓存区大小。默认是30MB
。每个Cache都应该有自己的一个缓冲区。 -
diskExpirTyhreadIntervalSeconds
:磁盘失效线程运行时间间隔,默认是120秒。 -
memoryStoreEvictionPolicy
:当达到maxElementsInMemory
限制时,Ehcache
将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)
。也可以设置为FIFO(先进先出
)或是LFU(较少使用)
。 -
clearOnFlush
:内存数量最大时是否清除。 -
memoryStoreEvictionPolicy
:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
-
1.3 修改启动类
添加注解@EnableCaching
,说明开启注解缓存
@SpringBootApplication
@EnableCaching
@MapperScan("cn.jzh.mapper")
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
1.4 缓存数据
1.4.1 @Cacheable
@Cacheable
注解在方法上,表示该方法的返回结果是可以缓存的。也就是说,该方法的返回结果会放在缓存中,以便于以后使用相同的参数
调用该方法时,会返回缓存中的值,而不会实际执行该方法。
注意
:这里强调了一点:参数相同
。
这一点应该是很容易理解的,因为缓存不关心方法的执行逻辑,它能确定的是:对于同一个方法,如果参数相同,那么返回结果也是相同的。但是如果参数不同,缓存只能假设结果是不同的,所以对于同一个方法,程序运行过程中,使用了多少种参数组合调用过该方法,理论上就会生成多少个缓存的 key(当然,这些组合的参数指的是与生成 key 相关的)
1.4.1.1 常见属性
@Cacheable
这个注解常用的几个属性:
-
cacheNames/value
:用来指定缓存组件的名字,二者选其一即可,若不指定则用默认缓存
示例::@Cacheable("menu")
- 关联多个缓存名
@Cacheable
支持同一个方法关联多个缓存。这种情况下,当执行方法之前,这些关联的每一个缓存都会被检查,而且只要至少其中一个缓存命中了,那么这个缓存中的值就会被返回
示例:@Cacheable({"menu", "menuById"})
- 关联多个缓存名
-
key
:缓存数据时使用的key
,可以用它来指定。默认是使用方法参数的值。(这个 key 你可以使用 spEL 表达式来编写) -
keyGenerator
:key 的生成器。key 和 keyGenerator 二选一使用
-
cacheManager
:可以用来指定缓存管理器。从哪个缓存管理器里面获取缓存。 -
condition
:可以用来指定符合条件的情况下才缓存
示例:@Cacheable(value="menu",condition="#id>1")
-
unless
:否定缓存。当unless
指定的条件为 true ,方法的返回值就不会被缓存。当然也可以获取到结果进行判断。(通过#result
获取方法结果)
示例:@Cacheable(value="menu",unless="#id>1")
执行后判断,不缓存的条件。unless 接收一个结果为 true 或 false 的表达式,表达式支持 SpEL。当结果为 true 时,不缓存
示例:@Cacheable(value = {"menuById"}, key = "#id", unless = "#result.type == 'folder'")
-
sync
:是否使用异步模式
是否同步,true/false
。在一个多线程的环境中,某些操作可能被相同的参数并发地调用,这样同一个value
值可能被多次计算(或多次访问 db),这样就达不到缓存的目的。针对这些可能高并发的操作,我们可以使用sync
参数来告诉底层的缓存提供者将缓存的入口锁住,这样就只能有一个线程计算操作的结果值,而其它线程需要等待,这样就避免了 n-1 次数据库访问。
sync = true
可以有效的避免缓存击穿的问题。
1.4.1.2 key & keyGenerator
需要提前注意指定key
,需要以#
打头
官方说 key
和 keyGenerator
参数是互斥的,同时指定两个会导致异常
-
keyGenerator
:
当我们在声明@Cacheable
时如果不指定key
参数,则该缓存名下的所有key
会使用KeyGenerator
根据参数自动生成。spring
有一个默认的SimpleKeyGenerator
,在spring boot
自动化配置中,这个会被默认注入。
默认的key 生成器
要求参数具有有效的hashCode() 和 equals()
方法实现。另外,keyGenerator
也支持自定义, 并通过keyGenerator
来指定
生成规则如下:- 如果该缓存方法没有参数,返回
SimpleKey.EMPTY
- 如果该缓存方法有一个参数,返回该参数的实例 ;
- 如果该缓存方法有多个参数,返回一个包含所有参数的
SimpleKey
- 如果该缓存方法没有参数,返回
-
key
:
相较于使用KeyGenerator
生成,spring
官方更推荐显式指定 key 的方式,即指定@Cacheable
的key
参数。
即便是显式指定,但是key
的值还是需要根据参数的不同来生成,那么如何实现动态拼接呢?SpEL(Spring Expression Language,Spring 表达式语言) 能做到这一点。
显示指定key,实现动态拼接
@Cacheable(value = "users",key = "#root.methodName+'[' + #id +']'")
public Object queryUserList(Integer id){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("id",1);
List<User> users = userMapper.selectList(wrapper);
return JSON.toJSON(users);
}
自定义keyGenerator
import org.springframework.cache.interceptor.KeyGenerator;
import java.lang.reflect.Method;
import java.util.Arrays;
@Configuration
public class MyCacheConfig{
@Bean("myKeyGenerator ")
public KeyGenerator keyGenerator(){
return new KeyGenerator(){
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toArray()+"]";
}
};
}
}
自定义keyGenerator
使用例子
@Cacheable(value = "users",keyGenerator = "myKeyGenerator")
public Object queryUserList(Integer id){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("id",1);
List<User> users = userMapper.selectList(wrapper);
return JSON.toJSON(users);
}
1.4.1.3 使用spEL编写key
前面说过,缓存的 key 支持使用 spEL 表达式去编写,下面总结一下使用 spEL 去编写 key 可以用的一些元数据
名字 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | root object | 当前被调用方法名 | #root.methodName |
method | root object | 当前被调用方法 | #root.method.name |
target | root object | 当前被调用目标对象 | #root.target |
targetClass | root object | 当前被调用目标对象类 | #root.targetClass |
args | root object | 当前被调用方法参数列表 | #root.args[0] |
caches | root object | 当前方法调用使用的缓存列表 ,如@Cacheable({"menu", "menuById"}) ,则有两个cache |
#root.caches[0].name |
argument name | evaluation context | 方法参数名字,可以直接使用 #参数名,也可以使用#p0或者#a0形式,0代表参数索引 | #a0 |
result | evaluation context | 方法执行后的返回值(仅当方法执行后的判断有效) | #result |
1.4.1.4 cacheManager & cacheResolver
CacheManager
,缓存管理器是用来管理(检索)一类缓存的。通常来讲,缓存管理器是与缓存组件类型相关联的。我们知道,spring
缓存抽象的目的是为使用不同缓存组件类型提供统一的访问接口,以向开发者屏蔽各种缓存组件的差异性。那么 CacheManager
就是承担了这种屏蔽的功能。spring 为其支持的每一种缓存的组件类型提供了一个默认的 manager
,如:RedisCacheManager 管理 redis 相关的缓存的检索、EhCacheManager 管理 ehCache 相关的缓等。
CacheResolver
,缓存解析器是用来管理缓存管理器的,CacheResolver
保持一个 cacheManager
的引用,并通过它来检索缓存。CacheResolver 与 CacheManager
的关系有点类似于 KeyGenerator 跟 key
。spring 默认提供了一个 SimpleCacheResolver
,开发者可以自定义并通过 @Bean
来注入自定义的解析器,以实现更灵活的检索。
大多数情况下,我们的系统只会配置一种缓存,所以我们并不需要显式指定 cacheManager 或者 cacheResolver
。但是 spring
允许我们的系统同时配置多种缓存组件,这种情况下,我们需要指定。指定的方式是使用 @Cacheable
的 cacheManager
或者 cacheResolver
参数。
注意
:按照官方文档,cacheManager
和 cacheResolver
是互斥参数,同时指定两个可能会导致异常
1.4.2 @CachePut
当需要在不影响方法执行的情况下更新缓存时,可以使用@CachePut
,也就是说,被 @CachePut
注解的缓存方法总是会执行,而且会尝试将结果放入缓存(当然,是否真的会缓存还跟一些注解参数有关,比如:unless 参数)。@CachePut
跟 @Cacheable
有相同的参数属性(但是没有 sync
属性)。@CachePut
更加适合于缓存填充,而不是方法执行流的优化。
由于与 @Cacheable
的属性基本相同,所以不再重复示例。这里重点说明一下它们的区别:
-
@Cacheable
的逻辑是:查找缓存->有就返回->没有就执行方法体->将结果缓存起来; -
@CachePut
的逻辑是:执行方法体 -> 将结果缓存起来;
所以 @Cacheable
适用于查询数据的方法,@CachePut
适用于更新数据的方法。
1.5 清除缓存@CacheEvict
1.5.1 常见属性
除了填充缓存,spring cache
也支持使用 @CacheEvict
来删除缓存。@CacheEvict
就是一个触发器,在每次调用被它注解的方法时,就会触发删除它指定的缓存的动作。跟 @Cacheable
和 @CachePut
一样,@CacheEvict
也要求指定一个或多个缓存,也指定自定义一的缓存解析器和 key
生成器,也支持指定条件(condition 参数)。
@CacheEvict
是用来清除缓存的,有以下属性:
-
value/cacheNames
:缓存位置名称,不能为空 -
key
:缓存的key,默认为空(如果allEntries=false,清除指定key的缓存)
一般来说,我们的更新操作只需要刷新缓存中某一个值,所以定义缓存的key值的方式就很重要,最好是能够唯一,因为这样可以准确的清除掉特定的缓存,而不会影响到其它缓存值 -
condition
:触发条件,只有满足条件的情况才会清除缓存,默认为空,支持SpEL -
allEntries
:true表示清除value中的全部缓存,默认为false -
beforeInvocation
:是否在执行对应方法之前删除缓存,默认false
(即执行方法之后再删除缓存)
1.5.2 beforeInvocation
beforeInvocation
是 @CacheEvict
中特有的一个属性,意为是否在执行对应方法之前删除缓存,默认 false
(即执行方法之后再删除缓存)。
首先思考一个问题,在什么情况下我们需要主动去删除缓存呢?一般来讲都是在删除数据的时候,需要主动去删除缓存。那么就存在一个问题,程序执行时顺序的,那我们到底是应该先删除缓存,再调用方法去数据库中删除;还是先从数据库中删除,完了之后再去删除对应的缓存呢?
在正常情况下,这两种方式差别并不大,毕竟程序执行都是毫秒级的,顺序执行没有什么时间跨度。但是,现实环境复杂,缓存访问和 db 访问都可能会出现异常,这种情况下就有区别了:
- 如果先删除缓存成功,然后 db 删除失败,那么接下来的查询就会直达数据库,造成压力;
- 如果先 db 删除成功,然后删除缓存失败,那么就会造成脏缓存;
至于该如何取舍,spring cache
通过 beforeInvocation
给开发者提供选择
网友评论