1.引入依赖
pom.xml增加依赖
<!-- 引入redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<!--引入springCache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
2.写配置文件
a.引入后自动配置?
CacheAutoConfiguration 会导入RedisCacheConfiguration 自动配置好了管理器 RedisCacheManager
b. 初始化缓存配置
【思路】 最终配置文件在最后步骤
spring.cache.type=redis
spring.redis.host=192.168.31.125
spring.redis.password=
spring.redis.port=6379
3.测试使用缓存
【—>】@Cacheable :触发将数据保存到缓存的操作
【—>】@CacheEvict: 触发将数据从缓存删除的操作
【—>】@CachePut: 不影响方法执行更新缓存
【—>】@Caching: 组合操作缓存
【—>】@CacheConfig: 在类级别(相同的类)上共享缓存配置
a.开启缓存 @EnableCaching
【思路】
@EnableCaching//后续不在这里
@SpringBootApplication
public class GulimallProductApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallProductApplication.class, args);
}
}
b.只需要注解就能开启缓存操作
【思路】@Cacheable :触发将数据保存到缓存的操作
【—>】@Cacheable({“category”})
调用数据 有缓存读取缓存并且方法不会被调用。 没有缓存就先写入缓存
保存一级菜单
//缓存的数据 需要制定放到名字的缓存【缓存的分区 按照业务类型分区】
@Cacheable({"category"}) //当前方法需要缓存 如果缓存中有 方法不用调用 如果方法中没有 调用方法并将方法结果放入缓存
@Override
public List<CategoryEntity> getLevel1Categorys() {
System.out.println("调用方法查询一级分类了");
return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid",0));
}
保存二级菜单
/**
* 用springCache替代 两个方法
* @return
*/
@Cacheable(value = {"category"},key="#root.methodName")
@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {
System.out.println("进入了查数据库逻辑");
List<CategoryEntity> selectList = baseMapper.selectList(null);
//1.查出所有1级分类
List<CategoryEntity> level1Categorys = getParent_cid(selectList, 0L);
//封装数据
Map<String, List<Catelog2Vo>> collect = level1Categorys.stream().collect(Collectors.toMap(k -> {
return k.getCatId().toString();
}, v -> {
//每一个的一级分类 查到这个一级分类的所有二级分类
List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
List<Catelog2Vo> catelog2Vos = null;
if (categoryEntities != null) {
catelog2Vos = categoryEntities.stream().map(level2 -> {
//查处当前二级分类下的所有三级分类
List<CategoryEntity> levl3Catelog = getParent_cid(selectList, level2.getCatId());
List<Catelog2Vo.Catelog3Vo> catelog3VoList = null;
if (levl3Catelog != null) {
catelog3VoList = levl3Catelog.stream().map(level3 -> {
new Catelog2Vo.Catelog3Vo("", "", "");
Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(
level2.getCatId().toString(),
level3.getCatId().toString(),
level3.getName()
);
return catelog3Vo;
}).collect(Collectors.toList());
}
Catelog2Vo catelog2Vo = new Catelog2Vo(
v.getCatId().toString(),
catelog3VoList,
level2.getCatId().toString(),
level2.getName()
);
return catelog2Vo;
}).collect(Collectors.toList());
}
return catelog2Vos;
}));
return collect;
}
默认行为:
缓存汇总有结果 这个备注接的方法就不会被调用了
缓存key自动生成 缓存名字: ::SimpleKey 缓存value值 默认使用jdk的序列化机制 将序列化后的数据存入redis
默认时间 -1 不过期
自定义:
1) 指定的生成缓存的key @Cacheable key接收的是表达式 key=“‘someRedisKey’” 或者用方法名key=“#root.method.name”
2) 指定缓存的过期时间 配置文件修改 spring.cache.redis.time-to-live=60000 单位ms
3) 数据保存为json格式
【思路】
修改配置方法:
@EnableCaching
@Configuration
public class MyCacheConfig {
@Bean
RedisCacheConfiguration redisCacheConfiguration(){
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// key value序列化机制
System.out.println("执行Cache的配置类");
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return config;
}
}
会导致配置文件失效 如下处理
原因如下代码展示: 注意那个return this.redisCacheConfiguration;
【思路】
if (this.redisCacheConfiguration != null) {
return this.redisCacheConfiguration;
}
Redis redisProperties = this.cacheProperties.getRedis();
org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
.defaultCacheConfig();
config = config.serializeValuesWith(SerializationPair
.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
代码指出,如果走了配置类 那么底下的几个if 就没有进行 我们把下面的几个信息需要重新写进配置类
原先的配置类样子:
【思路】
@ConfigurationProperties(prefix = "spring.cache")
public class CacheProperties {
...
给配置类加入注解 修改配置类
最终配置类
@EnableConfigurationProperties(CacheProperties.class)
@EnableCaching
@Configuration
public class MyCacheConfig {
/**
*1. 配置文件中的内容没有读取:
* @ConfigurationProperties(prefix = "spring.cache")
* public class CacheProperties {
* 2.让这个配置文件绑定生效 class注解导入:@EnableConfigurationProperties(CacheProperties.class)
* 使用A:
* @Autowired
* CacheProperties cacheProperties;
* 使用B:
* 当做参数传给方法
*
* @return
*/
@Bean
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//引用配置类 需要把配置文件都带过来
// config = config.entryTtl(Duration.ofSeconds(90));
// key value序列化机制
System.out.println("执行Cache的配置类");
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
//将配置文件中的内容拿过来
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
4.配置原理
CacheAutoConfiguration
-> RedisCacheConfiguration
-> 自动配置 RedisCacheMannager
->初始化所有的缓存
-> 每个缓存决定配置【RedisCacheConfiguration 有:就有 无:用默认】
修改缓存配置 需要给容器 放置一个RedisCacheConfiguration
-> 应用当前RedisCacheMannager 管理的所有缓存分区
最终的配置文件
redis.properties
spring.cache.type=redis
spring.redis.host=192.168.31.125
spring.redis.password=
spring.redis.port=6379
spring.cache.redis.time-to-live=180000
#spring.cache.redis.key-prefix=CACHE_
#spring.cache.redis.use-key-prefix=true
#是否缓存控制 解决缓存穿透问题
spring.cache.redis.cache-null-values=true
验证注解的修改数据:
【思路】
【—>】 @CacheEvict(value = {“category”},key=“‘getLevel1Categorys’”)
更新数据时 调用清除缓存 (读取时候 发现缓存为空 为给与填充)
//删除一级菜单
@CacheEvict(value = {"category"},key="'getLevel1Categorys'")
@Transactional
@Override
public void updateCascade(CategoryEntity category) {
this.updateById(category);
categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
}
【—>】@Caching: 组合操作演示
//删除一二级菜单
@Caching(evict = {
@CacheEvict(value = {"category"} ,key = "'getLevel1Categorys'"),
@CacheEvict(value = {"category"} ,key = "'getCatalogJson'")
})
@Transactional
@Override
public void updateCascade(CategoryEntity category) {
this.updateById(category);
categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
}
//删除整个分区的数据 一二级都在category分区
@CacheEvict(value = {"category"},allEntries = true)
@Transactional
@Override
public void updateCascade(CategoryEntity category) {
this.updateById(category);
categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
}
【—>】@CachePut: 不影响方法执行更新缓存
对比@CacheEvict 返回值的数据需要替换redis缓存内容 不需要在下次读数据发现没有再填入数据。它的要求是数据要有返回值。
SpringCache对缓存三大问题的处理:
三大问题
- 缓存穿透: 查询一个null数据(每次都跳过缓存查数据库)
- 缓存击穿: 大量数据进来同时查询数据库(高并发时,缓存过期导致高并发落到操作数据库上)。
- 缓存雪崩: 大量的key同时过期(大量缓存同时过期)。
缓存穿透:配置允许null缓存 spring.cache.redis.cache-null-values=true
缓存击穿: @Cacheable(value = {“category”},key=“#root.method.name”,sync = true)。sync = true参数会触发缓存的同步方法 这个锁是本地锁非分布式锁
缓存雪崩:设置缓存时间 配置:spring.cache.redis.time-to-live=180000
在缓存数据时,常规数据(都多写少,及时性一致性要求不高的数据),SpringCache是完全可以满足使用的。
但是由于spingCache没有加锁机制,而且没有分布式锁(微服务负载均衡n服务不可行)。像遇到及时性高数据,高频写数据等其需要特殊设计。可以考虑Rdisson的各种锁机制,或者启用canal 中间件订阅操作缓存等