Spring Boot Cache With Redis
原创大约 3 分钟
本篇文章将介绍如何使用 Redis 作为数据源,实现 Spring Boot 的方法缓存。
引入相关依赖
要实现方法缓存,首先需要引入 spring-boot-starter-cache
和 spring-boot-starter-data-redis
这两个依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
编写自动配置类
编写一个自动配置类,以注入默认的 RedisCacheConfiguration 和 自定义的配置 RedisCacheManagerBuilderCustomizer,并通过 @EnableCaching 启用 Spring Cache。
@Configuration
@ConditionalOnProperty(name = "spring.cache.type", havingValue = "redis")
@EnableConfigurationProperties({SpringCacheProperties.class})
public class SpringCacheConfiguration {
@Bean
public RedisCacheConfiguration cacheConfiguration() {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(mapper, Object.class);
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(SpringCacheProperties.DEFAULT_TTL_SECONDS))
// 禁止缓存 null value
.disableCachingNullValues()
// 禁用前缀
.disableKeyPrefix()
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
}
@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer(
SpringCacheProperties springCacheProperties) {
return (builder) -> builder
.withInitialCacheConfigurations(Optional.ofNullable(springCacheProperties.getEntries())
.orElse(Collections.emptyList()).stream()
.collect(Collectors.toMap(SpringCacheProperties.Entry::getCacheName,
entry -> cacheConfiguration().entryTtl(Duration.ofSeconds(entry.getTtlSeconds())))));
}
}
@Getter
@Setter
@ConfigurationProperties("spring-cache")
public class SpringCacheProperties {
public static final Long DEFAULT_TTL_SECONDS = 60L;
private List<Entry> entries;
@Getter
@Setter
public static class Entry {
private String cacheName;
private Long ttlSeconds;
}
}
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class CacheNameConstant {
/**
* 较短时间(几秒)
*/
public static final String SHORT_SECONDS = "SHORT_SECONDS";
/**
* 短时间(几分钟)
*/
public static final String SHORT_MINUTES = "SHORT_MINUTES";
/**
* 中等时间(几十分钟到及小时)
*/
public static final String SEVERAL_HOURS = "SEVERAL_HOURS";
/**
* 长时间(几天到一个月)
*/
public static final String SEVERAL_DAYS = "SEVERAL_DAYS";
/**
* 超长时间(几个月)
*/
public static final String EXTRA_LONG_MONTHS = "EXTRA_LONG_MONTHS";
/**
* 永不过期
*/
public static final String NO_EXPIRY = "NO_EXPIRY";
}
配置文件指定 Spring Cache 的类型
spring:
cache:
type: redis
spring-cache:
entries:
# 较短时间(几秒)
- cache-name: SHORT_SECONDS
ttl-seconds: 1
# 短时间(几分钟)
- cache-name: SHORT_MINUTES
ttl-seconds: 60
# 中等时间(几十分钟到及小时)
- cache-name: SEVERAL_HOURS
ttl-seconds: 3600
# 长时间(几天到一个月)
- cache-name: SEVERAL_DAYS
ttl-seconds: 86400
# 超长时间(几个月)
- cache-name: EXTRA_LONG_MONTHS
ttl-seconds: 2592000
# 永不过期
- cache-name: NO_EXPIRY
ttl-seconds: 0
使用
@Cacheable(value = CacheNameConstant.SEVERAL_DAYS,
key = "T(com.cpgege.constant.RedisKeyConstant).getRolePermissionsKey(#roleId)",
unless = "#result == null")
public List<SysPermissionDat> rolePermissions(Long roleId) {
return getBaseMapper().selectByRoleId(roleId);
}
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
@CacheEvict(value = CacheNameConstant.SEVERAL_DAYS,
key = "T(com.cpgege.constant.RedisKeyConstant).getRolePermissionsKey(#updateRoleDTO.id)")
public void updateRole(UpdateRoleDTO updateRoleDTO) {
// 查询角色
SysRoleDat role = getById(updateRoleDTO.getId());
Assert.notNull(role, "角色ID[{}]对应的角色不存在", updateRoleDTO.getId());
// 更新前校验
checkBeforeUpdate(role, updateRoleDTO);
SysRoleDat sysRoleDat = updateRoleDTO.toBean(SysRoleDat.class);
if (DataPermissionEnum.SPECIFIED_DEPARTMENT.getCode().equals(updateRoleDTO.getDataPermission())) {
sysRoleDat.setSpecifiedDeptIds(JSON.toJSONString(updateRoleDTO.getSpecifiedDeptIdList()));
}
updateById(sysRoleDat);
// permissionIds 为 null,则返回
if (updateRoleDTO.getPermissionIds() == null) {
return;
}
// 解绑角色下的权限
LambdaQueryWrapper<SysRolePermissionDat> wrapper = Wrappers.lambdaQuery();
sysRolePermissionDatService.remove(wrapper.eq(SysRolePermissionDat::getRoleId, updateRoleDTO.getId()));
// 保存角色与权限关联关系
batchSaveRolePermission(updateRoleDTO.getId(), updateRoleDTO.getPermissionIds(),
updateRoleDTO.getClientId(), updateRoleDTO.getCreateBy());
}
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
@CacheEvict(value = CacheNameConstant.SEVERAL_DAYS,
key = "T(com.cpgege.constant.RedisKeyConstant).getRolePermissionsKey(#roleId)")
public void removeRole(Long roleId) {
// 查询角色是否关联成员
LambdaQueryWrapper<AccountRoleDat> wrapper = Wrappers.lambdaQuery();
List<AccountRoleDat> accountRoles = accountRoleDatMapper.selectList(
wrapper.eq(AccountRoleDat::getRoleId, roleId));
Assert.isTrue(CollectionUtils.isEmpty(accountRoles), "请先移除角色成员");
// 删除角色下的所有权限
LambdaQueryWrapper<SysRolePermissionDat> rolePermissionQueryWrapper = Wrappers.lambdaQuery();
sysRolePermissionDatService.remove(rolePermissionQueryWrapper.eq(SysRolePermissionDat::getRoleId, roleId));
// 删除角色
removeById(roleId);
}
说明
org.springframework.data.redis.cache.RedisCache#put
@Override
public void put(Object key, @Nullable Object value) {
Object cacheValue = preProcessCacheValue(value);
if (!isAllowNullValues() && cacheValue == null) {
throw new IllegalArgumentException(String.format(
"Cache '%s' does not allow 'null' values; Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration",
name));
}
cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
}
如果禁止缓存 null value,且 @Cacheable 未配置 unless="#result == null",则抛出如下异常:
java.lang.IllegalArgumentException: Cache 'SEVERAL_HOURS' does not allow 'null' values; Avoid storing null via '@Cacheable(unless="#result == null")' or configure RedisCache to allow 'null' via RedisCacheConfiguration
如果未禁止缓存 null value,且未配置 unless="#result == null",当 result = null 时,会缓存 null 值,缓存的 value 为如下:
��sr+org.springframework.cache.support.NullValuexp
如果未禁止缓存 null value,且 @Cacheable 配置了 unless="#result == null",当 result = null 时,不会缓存 null 值。