<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
spring:
cache:
#指定缓存类型为redis
type: redis
redis:
# 指定redis中的过期时间为1h
time-to-live: 3600000
# 每一个缓存的前缀,方便和普通使用的redis存的内容区分
# 建议默认使用分区名作为前缀即可,这样数据在redis中以友好的:冒号在可视化软件中方便查看
#key-prefix: CACHE_
# 是否使用缓存名前缀,建议打开
use-key-prefix: true
# 是否缓存空值,可以有效防止缓存穿透
cache-null-values: true
# 是否开启缓存统计,默认就是false
enable-statistics: false
import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
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.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
//CacheProperties本身是没有注入spring容器的,为了能取到
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
//单纯的开启缓存把这个注解写在启动类上就行了
@EnableCaching
public class MyCacheConfig {
// @Autowired
// CacheProperties cacheProperties;
/**
* 使用自己的RedisCacheConfiguration,application.yml中的配置就不会应用上了
* 原始详见org.springframework.boot.autoconfigure.cache的80行
* 其中使其取值生效的写法:
* @ConfigurationProperties(prefix = "spring.cache")
* public class CacheProperties
* 我使用注解 @EnableConfigurationProperties(CacheProperties.class)
* 在形参上传入CacheProperties cacheProperties,它会自动找spring容器要这个对象
* 也可以写个成员属性@Autowrite
* @return
*/
@Bean
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//使存入redis中的 k-v 按我们指定的格式存入,默认的方式,Value会是jdk的序列化机制,Value根本没有可读性,这里使用的是阿里巴巴的通用型fastjson,也可以用org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;spring自带的通用json
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer()));
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
//自己手动让application.yml中的配置生效
//与源码一致,详细作用见源码
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
@Cacheable 触发将数据保存到缓存的操作
@CacheEvict 触发将数据移除缓存的操作
@CachePut 更新缓存而不会干扰方法执行
@Caching 重新组合要在方法上应用的多个缓存操作
@CacheConfig 在类级别共享一些常见的缓存相关设置
更新操作:
/**
* @CacheEvict:失效模式,key加单引号,不加单引号一律以为动态取值SpEl表达式
* allEntries = true,表示删除category分区下的所有缓存
* 所以约定:只要存储同一类型的数据,都可以指定到同一个分区,删除就可以批量删除
* @CachePut:双写模式下使用,因为该方法没有返回值,所以不具备再次写入新值的能力
* @Caching:组合操作
* @param category
*/
//@CacheEvict(value = "category",key = "'getLevel1Categorys'")
//@CacheEvict(value = "category",allEntries = true)
//@CachePut("………………")
@Caching(evict = {
@CacheEvict(value = "category",key = "'getLevel1Categorys'"), //一级菜单
@CacheEvict(value = "category", key = "'getCatalogJson'") //所有菜单json格式
})
@Override
@Transactional
public void updateCategoryDetail(CategoryEntity category) {
//本表
this.updateById(category);
//同步更新其它关联表的冗余数据
//分类名不为空
if (!StringUtils.isEmpty(category.getName())) {
//品牌分类关联表
categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
}
}
查询操作:
/**
* @Cacheable(value = {"分区名"}, key = "自定义,可以用SpEL表达式,#开头,也可以自己写,需要单引号")
* @return
*/
@Cacheable(value = {"category"}, key = "#root.methodName")
@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {
List<CategoryEntity> selectList = baseMapper.selectList(null);
//找出所有1级分类
List<CategoryEntity> level1Categorys = getParentCid(selectList, 0L);
Map<String, List<Catelog2Vo>> map = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//当前拿到的是1级分类,查到它的2级分类
List<CategoryEntity> level2 = getParentCid(selectList, v.getCatId());
//封装上面的结果
List<Catelog2Vo> collect = null;
if (level2 != null && level2.size() > 0) {
collect = level2.stream().map(l2 -> {
Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
//找当前二级分类的三级分类
List<CategoryEntity> level3 = getParentCid(selectList, l2.getCatId());
if (level3 != null && level3.size() > 0) {
//封装成指定格式
List<Catelog2Vo.Catelog3Vo> catelog3Vos = level3.stream().map(l3 -> {
Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(
l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
return catelog3Vo;
}).collect(Collectors.toList());
catelog2Vo.setCatalog3List(catelog3Vos);
}
return catelog2Vo;
}).collect(Collectors.toList());
}
return collect;
}));
return map;
}
1)、读模式
缓存穿透:查询一个null数据。解决方案:缓存空数据,可通过spring.cache.redis.cache-null-values=true
缓存击穿:大量并发进来同时查询一个正好过期的数据。解决方案:加锁 ? 默认是无加锁的;
使用sync = true来解决击穿问题
缓存雪崩:大量的key同时过期。解决:加随机时间。
2)、写模式:(缓存与数据库一致)
读写加锁。
引入Canal,感知到MySQL的更新去更新Redis
读多写多,直接去数据库查询就行
3)、总结:
常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache):
写模式(只要缓存的数据有过期时间就足够了)
特殊数据:特殊设计