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