Spring Cache设置缓存条件
原理
从Spring3.1开始,Spring框架提供了对Cache的支持,提供了一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的作用。
提供的主要注解有@Cacheable、@CachePut、@CacheEvict和@Caching,具体见下表:
注解 | 说明 |
---|---|
@Cacheable | 可以标注在类或方法上:标注在方法上表示该方法支持数据缓存;标在类上表示该类的所有方法都支持数据缓存。 具体功能:在执行方法体之前,检查缓存中是否有相同key值的缓存存在,如果存在对应的缓存,直接返回缓存中的值;如果不存在对应的缓存,则执行相应的方法体获取数据,并将数据存储到缓存中。 |
@CachePut | 可以标注在类或方法上,表示支持数据缓存。 具体功能:在方法执行前不会检查缓存中是否存在相应的缓存,而是每次都会执行方法体,并将方法执行结果存储到缓存中,如果相应key值的缓存存在,则更新key对应的value值。 |
@CacheEvict | 可以标注在类或方法上,用于清除相应key值的缓存。 |
@Caching | 可以标注在类或方法上,它有三个属性cacheable、put、evict分别用于指定@Cacheable、@CachePut和@CacheEvict |
当需要在类上或方法上同时使用多个注解时,可以使用@Caching,如:
1
|
@Caching (cacheable= @Cacheable ( "User" ), evict = { @CacheEvict ( "Member" ), @CacheEvict (value = "Customer" , allEntries = true )})
|
@Cacheable的常用属性及说明
如下表所示:
@Cacheable属性 | 说明 |
---|---|
key | 表示缓存的名称,必须指定且至少要有一个值,比如:@Cacheable(value=“Dept”)或@Cacheable(value={“Dept”,“Depts”}) |
condition | 表示是否需要缓存,默认为空,表示所有情况都会缓存。通过SpEL表达式来指定,若condition的值为true则会缓存,若为false则不会缓存,如@Cacheable(value=“Dept”,key="‘deptno_'+# deptno “,condition=”#deptno<=40") |
value | 表示缓存的key,支持SpEL表达式,如@Cacheable(value=“Dept”,key="‘deptno_' +#deptno"),可以不指定值,如果不指定,则缺省按照方法的所有参数进行组合。除了上述使用方法参数作为key之外,Spring还提供了一个root对象用来生成key,使用方法如下表所示,其中"#root"可以省略。 |
Root对象
Root对象 | 说明 |
---|---|
methodName | 当前方法名,比如#root.methodName |
method | 当前方法,比如#root.method.name |
target | 当前被调用的对象,比如#root.target |
targetClass | 当前被调用的对象的class,比如#root.targetClass |
args | 当前方法参数组成的数组,比如#root.args[0] |
caches | 当前被调用的方法使用的缓存,比如#root.caches[0].name |
@CachePut的常用属性同@Cacheable
@CacheEvict的常用属性如下表所示:
@CacheEvict属性 | 说明 |
---|---|
value | 表示要清除的缓存名 |
key | 表示需要清除的缓存key值, |
condition | 当condition的值为true时才清除缓存 |
allEntries | 表示是否需要清除缓存中的所有元素。默认为false,表示不需要,当指定了allEntries为true时,将忽略指定的key。 |
beforeInvocation | 清除操作默认是在方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当该属性值为true时,会在调用该方法之前清除缓存中的指定元素。 |
示例:设置当 dname 的长度大于3时才缓存
1
2
3
4
5
6
7
8
9
10
|
//条件缓存
@ResponseBody
@GetMapping ( "/getLocByDname" )
@Cacheable (cacheNames = "dept" , key = "#dname" , condition = "#dname.length()>3" )
public String getLocByDname( @RequestParam ( "dname" ) String dname) { //key动态参数
QueryWrapper<Dept> queryMapper = new QueryWrapper<>();
queryMapper.eq( "dname" , dname);
Dept dept = deptService.getOne(queryMapper);
return dept.getLoc();
}
|
示例:unless 即条件不成立时缓存
#result 代表返回值,意思是当返回码不等于 200 时不缓存,也就是等于 200 时才缓存。
1
2
3
4
5
6
7
8
9
10
11
12
|
@ResponseBody
@GetMapping ( "/getDeptByDname" )
@Cacheable (cacheNames = "dept" , key = "#dname" , unless = "#result.code != 200" )
public Result<Dept> getDeptByDname( @RequestParam ( "dname" ) String dname){ //key动态参数
QueryWrapper<Dept> queryMapper = new QueryWrapper<>();
queryMapper.eq( "dname" , dname);
Dept dept = deptService.getOne(queryMapper);
if (dept == null )
return ResultUtil.error( 120 , "dept is null" );
else
return ResultUtil.success(dept);
}
|
Cache缓存配置
1、pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
< dependency >
< groupId >org.springframework.boot</ groupId >
< artifactId >spring-boot-starter-cache</ artifactId >
</ dependency >
< dependency >
< groupId >org.springframework.boot</ groupId >
< artifactId >spring-boot-starter-aop</ artifactId >
</ dependency >
<!-- 反射工具类用于手动扫描指定包下的注解,根据defaultCache模块增加ehcache缓存域(非Spring Cache必须)-->
<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
< dependency >
< groupId >org.reflections</ groupId >
< artifactId >reflections</ artifactId >
< version >0.9.11</ version >
</ dependency >
|
2、Ehcache配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
<? xml version = "1.0" encoding = "UTF-8" ?>
< ehcache xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation = "http://ehcache.org/ehcache.xsd"
updateCheck = "false" >
<!-- 磁盘缓存位置 -->
< diskStore path = "java.io.tmpdir" />
<!--
name:缓存名称。
maxElementsInMemory:缓存最大个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxElementsOnDisk:硬盘最大缓存个数。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
-->
<!-- 默认缓存 -->
< defaultCache
eternal = "false"
maxElementsInMemory = "200000"
overflowToDisk = "false"
diskPersistent = "false"
timeToIdleSeconds = "0"
timeToLiveSeconds = "600"
memoryStoreEvictionPolicy = "LRU" />
</ ehcache >
|
3、配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
@Configuration
@EnableCaching
public class CustomConfiguration {
/**
* @see org.springframework.cache.interceptor.SimpleKeyGenerator
* Generate a key based on the specified parameters.
*/
public static Object generateKey(Object... params) {
if (params.length == 0 ) {
return SimpleKey.EMPTY;
}
if (params.length == 1 ) {
Object param = params[ 0 ];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
/**
* 若将target作为key的一部分时,CGLIB动态代理可能导致重复缓存
* 注意:返回的key一定要重写hashCode()和toString(),防止key对象不一致导致的缓存无法命中
* 例如:ehcache 底层存储net.sf.ehcache.store.chm.SelectableConcurrentHashMap#containsKey
*/
@Bean
public KeyGenerator customKeyGenerator(){
return (target, method, params) -> {
final Object key = generateKey(params);
StringBuffer buffer = new StringBuffer();
buffer.append(method.getName());
buffer.append( "::" );
buffer.append(key.toString());
// 注意一定要转为String,否则ehcache key对象可能不一样,导致缓存无法命中
return buffer.toString();
};
}
/**
* redis缓存管理器
*/
@Bean
@ConditionalOnBean (RedisConfiguration. class )
@ConditionalOnProperty (prefix = "spring.cache" , name = "type" , havingValue = "redis" ,
matchIfMissing = false )
public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer( new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer( new GenericJackson2JsonRedisSerializer()))
.entryTtl(Duration.ofMinutes( 10 ));
return RedisCacheManager
.builder(RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(config).build();
}
/**
* ehcache缓存管理器(默认)
* default XML files {@link net.sf.ehcache.config.ConfigurationFactory#parseConfiguration()}
*/
@Bean
@ConditionalOnProperty (prefix = "spring.cache" , name = "type" , havingValue = "ehcache" ,
matchIfMissing = true )
public CacheManager ehcacheCacheManager() {
net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.create();
/**
* 包扫描查找指定注解并将cacheNames添加到net.sf.ehcache.CacheManager(单例)
*/
Reflections reflections = new Reflections( "com.example.demo.service" , new TypeAnnotationsScanner()
, new SubTypesScanner(), new MethodAnnotationsScanner());
Set<Class<?>> classesList = reflections.getTypesAnnotatedWith(CacheConfig. class );
for (Class<?> aClass : classesList) {
final CacheConfig config = AnnotationUtils.findAnnotation(aClass, CacheConfig. class );
if (config.cacheNames() != null && config.cacheNames().length > 0 ) {
for (String cacheName : config.cacheNames()) {
cacheManager.addCacheIfAbsent(cacheName);
}
}
}
/**
* 方法级别的注解 @Caching、@CacheEvict、@Cacheable、@CachePut,结合实际业务场景仅扫描@Cacheable即可
*/
final Set<Method> methods = reflections.getMethodsAnnotatedWith(Cacheable. class );
for (Method method : methods) {
final Cacheable cacheable = AnnotationUtils.findAnnotation(method, Cacheable. class );
if (cacheable.cacheNames() != null && cacheable.cacheNames().length > 0 ) {
for (String cacheName : cacheable.cacheNames()) {
cacheManager.addCacheIfAbsent(cacheName);
}
}
}
EhCacheCacheManager ehCacheCacheManager = new EhCacheCacheManager();
ehCacheCacheManager.setCacheManager(cacheManager);
return ehCacheCacheManager;
}
}
|
4、示例
1
2
3
4
5
6
7
8
9
10
11
12
|
@Component
@CacheConfig (cacheNames = "XXXServiceImpl" , keyGenerator = "customKeyGenerator" )
public class XXXServiceImpl extends ServiceImpl<XXXMapper, XXXEntity> implements XXXService {
@CacheEvict (allEntries = true )
public void evictAllEntries() {}
@Override
@Cacheable
public List<XXXEntity> findById(Long id) {
return this .baseMapper.selectList( new QueryWrapper<XXXEntity>().lambda()
.eq(XXXEntity::getId, id));
}
}
|
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://hcshow.blog.csdn.net/article/details/119271227