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;
@Slf4j @EnableCaching @Configuration public class RedisConfig extends CachingConfigurerSupport {
@Resource private LettuceConnectionFactory lettuceConnectionFactory;
@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();
redisTemplate.setKeySerializer(stringSerializer); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashKeySerializer(stringSerializer); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet(); return redisTemplate; }
@Bean public CacheManager cacheManager(LettuceConnectionFactory factory) { Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = jacksonSerializer(); RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(6)); RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
RedisCacheWriter writer = new JeecgRedisCacheWriter(factory, Duration.ofMillis(50L)); 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(); return cacheManager; }
@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
|
@Cacheable(value = CacheConstant.SYS_DICT_CACHE,key = "#code", unless = "#result == null") public String queryDictTextByKey(String code) { }
@Cacheable(value = CacheConstant.SYS_DICT_CACHE,key = "#code+':'+#key", unless = "#result == null ") public String queryDictTextByKey(String code, String key) { }
@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)
@CacheEvict(value= {CacheConstant.SYS_USERS_CACHE}, key="#username") public void updateUserDepart(String username, String orgCode) { baseMapper.updateUserDepart(username, orgCode); }
Set keys3 = redisTemplate.keys(CacheConstant.SYS_DEPARTS_CACHE + "*"); redisTemplate.delete(keys3);
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;
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; }
@CacheEvict(value= { CACHE_KEY }, allEntries=true) public Result<?> cacheEvict() { return Result.OK("CacheEvict"); }
@CacheEvict(value= { CACHE_KEY }, key="#eventcode") public Result<?> cacheEvict(String eventcode) { return Result.OK("CacheEvictByEventCode"); }
public Result<?> deleteCache(String eventcode) { redisUtil.del(CACHE_KEY + "::" + eventcode); return Result.OK("deleteCacheByEventcode"); }
@Autowired private RedisTemplate redisTemplate;
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); }
@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); }
@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 可以使缓存失效