Spring Cache

https://www.cnblogs.com/fashflying/p/6908028.html

Cache 存储方式

CacheManager 是 Spring 定义的一个用来管理 Cache 的接口。Spring 自身已经为我们提供了两种 CacheManager 的实现,一种是基于 Java API 的 ConcurrentMap,另一种是基于Ehcache的 Cache 实现。如果我们需要使用其它类型的缓存时,我们可以自己来实现 Spring 的 CacheManager 接口或 AbstractCacheManager 抽象类。下面分别来看看 Spring 已经为我们实现好了的两种 CacheManager 的配置示例

基于 Java API 的 ConcurrentMap

1
2
3
4
5
6
7
8
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="xxx"/>
</set>
</property>
</bean>
##上面的配置使用的是一个 SimpleCacheManager,其中包含一个名为 “xxx” 的 ConcurrentMapCache

基于 Ehcache 的 Cache 实现

1
2
3
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcacheManager"/>

<bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache-spring.xml"/>

基于 Redis 的 Cache 实现【推荐】

RedisConfig.java

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package org.jeecg.common.modules.redis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import lombok.extern.slf4j.Slf4j;

import org.jeecg.common.constant.CacheConstant;
import org.jeecg.common.constant.GlobalConstants;

import org.jeecg.common.modules.redis.receiver.RedisReceiver;
import org.jeecg.common.modules.redis.writer.JeecgRedisCacheWriter;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.*;

import javax.annotation.Resource;
import java.time.Duration;

import static java.util.Collections.singletonMap;

/**
* 开启缓存支持
* @author zyf
* @Return:
*/
@Slf4j
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

@Resource
private LettuceConnectionFactory lettuceConnectionFactory;

// /**
// * @description 自定义的缓存key的生成策略 若想使用这个key
// * 只需要讲注解上keyGenerator的值设置为keyGenerator即可</br>
// * @return 自定义策略生成的key
// */
// @Override
// @Bean
// public KeyGenerator keyGenerator() {
// return new KeyGenerator() {
// @Override
// public Object generate(Object target, Method method, Object... params) {
// StringBuilder sb = new StringBuilder();
// sb.append(target.getClass().getName());
// sb.append(method.getDeclaringClass().getName());
// Arrays.stream(params).map(Object::toString).forEach(sb::append);
// return sb.toString();
// }
// };
// }

/**
* RedisTemplate配置
* @param lettuceConnectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
log.info(" --- redis config init --- ");
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = jacksonSerializer();
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
RedisSerializer<String> stringSerializer = new StringRedisSerializer();

// key序列化
redisTemplate.setKeySerializer(stringSerializer);
// value序列化
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// Hash key序列化
redisTemplate.setHashKeySerializer(stringSerializer);
// Hash value序列化
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}

/**
* 缓存配置管理器
*
* @param factory
* @return
*/
@Bean
public CacheManager cacheManager(LettuceConnectionFactory factory) {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = jacksonSerializer();
// 配置序列化(解决乱码的问题),并且配置缓存默认有效期 6小时
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(6));
RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
//.disableCachingNullValues();

// 以锁写入的方式创建RedisCacheWriter对象
//update-begin-author:taoyan date:20210316 for:注解CacheEvict根据key删除redis支持通配符*
RedisCacheWriter writer = new JeecgRedisCacheWriter(factory, Duration.ofMillis(50L));
//RedisCacheWriter.lockingRedisCacheWriter(factory);
// 创建默认缓存配置对象
/* 默认配置,设置缓存有效期 1小时*/
//RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));
/* 自定义配置test:demo 的超时时间为 5分钟*/
RedisCacheManager cacheManager = RedisCacheManager.builder(writer).cacheDefaults(redisCacheConfiguration)
.withInitialCacheConfigurations(singletonMap(CacheConstant.SYS_DICT_TABLE_CACHE,
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)).disableCachingNullValues()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))))
.withInitialCacheConfigurations(singletonMap(CacheConstant.TEST_DEMO_CACHE, RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(5)).disableCachingNullValues()))
.withInitialCacheConfigurations(singletonMap(CacheConstant.PLUGIN_MALL_RANKING, RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(24)).disableCachingNullValues()))
.withInitialCacheConfigurations(singletonMap(CacheConstant.PLUGIN_MALL_PAGE_LIST, RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(24)).disableCachingNullValues()))
.transactionAware().build();
//update-end-author:taoyan date:20210316 for:注解CacheEvict根据key删除redis支持通配符*
return cacheManager;
}

/**
* redis 监听配置
*
* @param redisConnectionFactory redis 配置
* @return
*/
@Bean
public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory, RedisReceiver redisReceiver, MessageListenerAdapter commonListenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
container.addMessageListener(commonListenerAdapter, new ChannelTopic(GlobalConstants.REDIS_TOPIC_NAME));
return container;
}


@Bean
MessageListenerAdapter commonListenerAdapter(RedisReceiver redisReceiver) {
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(redisReceiver, "onMessage");
messageListenerAdapter.setSerializer(jacksonSerializer());
return messageListenerAdapter;
}

private Jackson2JsonRedisSerializer jacksonSerializer() {
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return jackson2JsonRedisSerializer;
}
}

使用

缓存

方法上添加注解 @Cacheable

cacheNames 和 value 一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 带参数 code
// sys:cache:dict::sex
@Cacheable(value = CacheConstant.SYS_DICT_CACHE,key = "#code", unless = "#result == null")
public String queryDictTextByKey(String code) {
}

// 带参数 code 和 key
// sys:cache:dict::sex:1
@Cacheable(value = CacheConstant.SYS_DICT_CACHE,key = "#code+':'+#key", unless = "#result == null ")
public String queryDictTextByKey(String code, String key) {
}

// 缓存Key为:sys:cache:user::zhangsan
@Cacheable(cacheNames=CacheConstant.SYS_USERS_CACHE, key="#username")
public LoginUser getUserByName(String username) {
}

条件缓存 condition 和 unless

condition: 满足XXX条件时缓存。即缓存条件

有时候,一些值不适合缓存,可以使用 @Cacheable 的 condition 属性判读那些数据不缓存,它接收的是一个 Spel 表达式,该表达式的值是 true 或 false;true,数据被缓存,false不被缓存

key 必须是 "glmapper::" 开头的才允许缓存

1
2
3
4
5
@Cacheable(value = CacheConstant.SYS_DICT_CACHE, key = "#key", condition = "#key.startsWith('glmapper::')")
public String find(String key) {
System.out.println("execute find...");
return this.mockDao.find(key);
}

unless: 排除XXX情况时缓存。即不缓存条件

@Cacheable#unless 一般是对结果条件判读是否进行缓存使用的,这个示例使用的是入参作为判断条件,各位可以自己写一个根据结果进行缓存的示例,切记满足条件是不缓存。Spel #result变量代表返回值

如果返回结果是 null,则不缓存

1
2
3
4
5
6
@CachePut(unless="#result == null", keyGenerator = "myKeyGenerator")
public String save(String model) {
System.out.println("execute save...");
this.mockDao.save(model, model);
return model;
}

失效

方法上添加注解 @CacheEvict

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 指定缓存失效
@CacheEvict(value= {CacheConstant.SYS_DICT_CACHE}, allEntries=true)

// 指定多个缓存失效
@CacheEvict(value={CacheConstant.SYS_DICT_CACHE, CacheConstant.SYS_ENABLE_DICT_CACHE}, allEntries=true)

// 使缓存Key sys:cache:user::zhangsan 失效
@CacheEvict(value= {CacheConstant.SYS_USERS_CACHE}, key="#username")
public void updateUserDepart(String username, String orgCode) {
baseMapper.updateUserDepart(username, orgCode);
}

// 查找以指定 key 开头的缓存并清除
Set keys3 = redisTemplate.keys(CacheConstant.SYS_DEPARTS_CACHE + "*");
redisTemplate.delete(keys3);

// 手动删除缓存
// redisUtil.del("sys:cache:user::zhangsan");
redisUtil.del(String.format("%s::%s", CacheConstant.SYS_USERS_CACHE, sysUser.getUsername()));

示例

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
private final String CACHE_KEY = "hello:world";

// 缓存
// 注解方式:无参
@Cacheable(value = CACHE_KEY)
public List<Event> getDataList() {
List<Event> list = this.list(new LambdaQueryWrapper<Event>().eq(Event::getActStatus, ActivitiConstant.STATUS_TO_APPLY));
return list;
}

// 注解方式:带参
@Cacheable(value = CACHE_KEY, key = "#eventcode", unless = "#result == null ")
public List<Event> getDataListCached(String eventcode) {
List<Event> list = this.list(new LambdaQueryWrapper<Event>().eq(Event::getEventcode, eventcode));
return list;
}

@Autowired
private RedisUtil redisUtil;

// redisUtil 方式
public List<Event> getDataList(String eventcode) {
String cacheKey = CACHE_KEY + "::" + eventcode;
String data = (String) redisUtil.get(cacheKey);
if(data != null) {
List<Event> list = JSON.parseArray(data, Event.class);
return list;
}

List<Event> list = this.list(new LambdaQueryWrapper<Event>().eq(Event::getEventcode, eventcode));
if(CollectionUtil.isNotEmpty(list)) {
redisUtil.set(cacheKey, JSON.toJSONString(list));
}
return list;
}

// 删除缓存
// 注解方式:使KEY下所有缓存失效
@CacheEvict(value= { CACHE_KEY }, allEntries=true)
public Result<?> cacheEvict() {
return Result.OK("CacheEvict");
}

// 注解方式:使KEY下指定缓存失效
@CacheEvict(value= { CACHE_KEY }, key="#eventcode")
public Result<?> cacheEvict(String eventcode) {
return Result.OK("CacheEvictByEventCode");
}

// redisUtil 方式:删除KEY下指定缓存
public Result<?> deleteCache(String eventcode) {
redisUtil.del(CACHE_KEY + "::" + eventcode);
return Result.OK("deleteCacheByEventcode");
}

@Autowired
private RedisTemplate redisTemplate;

// redisTemplate 方式:删除KEY下所有缓存
public Result<?> deleteCache2() {
Set keys3 = redisTemplate.keys(CACHE_KEY + "*");
redisTemplate.delete(keys3);
return Result.OK("deleteCache2");
}
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

private final String KEY = "hello:world";

@GetMapping(value = "/testCache")
@Cacheable(value = KEY) // 缓存
public Result<?> testCache() {
List<Event> list = eventService.list(new LambdaQueryWrapper<Event>().eq(Event::getActStatus, ActivitiConstant.STATUS_TO_APPLY));
return Result.OK("操作成功", list);
}

// hello:world::null:null
// hello:world::S2022102700004:null
// hello:world::null:1
// hello:world::S2022102700004:1
@GetMapping(value = "/testCache")
@Cacheable(value = KEY, key = "#eventcode+':'+#sourcecode", unless = "#result == null")
public Result<?> testCache(@RequestParam(required = false) String eventcode,
@RequestParam(required = false) String sourcecode) {
List<Event> list = eventService.list(new LambdaQueryWrapper<Event>()
.eq(StrUtil.isNotBlank(eventcode), Event::getEventcode, eventcode)
.eq(StrUtil.isNotBlank(sourcecode), Event::getSourcecode, sourcecode));
return Result.OK("操作成功", list);
}

// 注解方式使KEY下所有缓存失效
@GetMapping(value = "/cacheEvict")
@CacheEvict(value= { KEY }, allEntries=true)
public Result<?> cacheEvict() {
return Result.OK("CacheEvict");
}

注意

  • 通过 redisUtil set KEY 放缓存,通过 CacheEvict KEY 无法使缓存失效
  • 通过 redisUtil set KEY + “::” + 参数缓存,通过 CacheEvict KEY 可以使缓存失效