对于缓存声明,Spring的缓存抽象提供了一组Java注解
@Cacheable: Triggers cache population:触发将数据保存到缓存的操作
@CacheEvict: Triggers cache eviction: 触发将数据从缓存删除的操作
@CachePut: Updates the cache without interfering with the method execution:不影响方法执行更新缓存
@Caching: Regroups multiple cache operations to be applied on a method:组合以上多个操作
@CacheConfig: Shares some common cache-related settings at class-level:在类级别共享缓存的相同配置
1、引入maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<!-- Lettuce的bug导致netty堆外内存溢出 切换使用jedis-->
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Lettuce的bug导致netty堆外内存溢出 切换使用jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!-- 整合SringCache 简化缓存开发-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2、在application.properties中添加配置信息
#以下是整合Spring Cache 的相关配置
#配置缓存的类型 (最简化的配置)
spring.cache.type=redis
#指定缓存的名字
#spring.cache.cache-names=qq,
#指定缓存的存活时间 单位:ms
spring.cache.redis.time-to-live=3600000
#为了区分redis其他的东西
#如果指定了前缀,就用我们指定的前缀,如果没有就默认使用缓存的名字(分区名-value)作为前缀
#优先级高
#spring.cache.redis.key-prefix=CACHE_
#默认是使用前缀的
spring.cache.redis.use-key-prefix=true
#是否缓存空值 防止缓存穿透
spring.cache.redis.cache-null-values=true
3、添加配置类
//绑定配置文件的配置
@EnableConfigurationProperties(CacheProperties.class)
//开启缓存功能
@EnableCaching
@Configuration
public class MyCacheConfig {
// @Autowired
// CacheProperties cacheProperties;
/**
* 没有用上配置文件中的东西
* 1)、原来配置文件的绑定配置是这样子的
* @ConfigurationProperties(prefix = "spring.cache")
* public class CacheProperties {
* 2)、要让他生效
* @EnableConfigurationProperties(CacheProperties.class)
* @return
*/
@Bean
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// config = config.entryTtl();
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、编写查询测试:并且配置了防止缓存穿透和缓存击穿
/**
* 1、每一个需要缓存的数据我们都需要指定放到那个名字的缓存【缓存分区的划分【按照业务类型划分】】
* 2、@Cacheable({"category"})
* 代表当前方法的结果需要缓存,如果缓存中有,方法不调用
* 如果缓存中没有,调用方法,最后将方法的结果放入缓存
* 3、默认行为:
* 1、如果缓存中有,方法不用调用
* 2、key默自动生成,缓存的名字:SimpleKey[](自动生成的key值)
* 3、缓存中value的值,默认使用jdk序列化,将序列化后的数据存到redis
* 3、默认的过期时间,-1
*
* 自定义操作
* 1、指定缓存使用的key key属性指定,接收一个SpEl
* 2、指定缓存数据的存活时间 配置文件中修改ttl
* 3、将数据保存为json格式
* 4、Spring-Cache的不足:
* 1、读模式:
* 缓存穿透:查询一个null数据,解决 缓存空数据:ache-null-values=true
* 缓存击穿:大量并发进来同时查询一个正好过期的数据,解决:加锁 ? 默认是无加锁
* 缓存雪崩:大量的key同时过期,解决:加上随机时间,Spring-cache-redis-time-to-live
* 2、写模式:(缓存与数据库库不一致)
* 1、读写加锁
* 2、引入canal,感知到MySQL的更新去更新数据库
* 3、读多写多,直接去数据库查询就行
*
* 总结:
* 常规数据(读多写少,即时性,一致性要求不高的数据)完全可以使用SpringCache 写模式( 只要缓存数据有过期时间就足够了)
*
* 特殊数据:特殊设计
* 原理:
* CacheManager(RedisManager) -> Cache(RedisCache) ->Cache负责缓存的读写
* sync = true解决缓存击穿 默认是false
*/
@Cacheable(value = {"category"}, key = "#root.method.name", sync = true)
@Override
public List<CategoryEntity> getLevel1Catrgorys() {
//测试spring cache
System.out.println("public List<CategoryEntity> getLevel1Catrgorys() {....");
//压力测试 数据库navicat增加了 parent_cid 为索引
// long l = System.currentTimeMillis();
List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
// System.out.println("消耗时间->:" + (System.currentTimeMillis()-l) + "ms");
return categoryEntities;
// return null;//测试缓存空值
}
如果我们在修改或者删除的数据的时候需要删除缓存,又该怎做呢?
5、编写删除缓存测试:
将category这个分区下的所有数据都删除,也还可以使用如下注解方式删除缓存
@Caching(evict = {
@CacheEvict(value = "category", key = "'getLevel1Catrgorys'"),
@CacheEvict(value = "category", key = "'getCatalogJson'")
})
来删除多个缓存值。
//存储同一类型的数据,都可以指定成同一个分区 分区名默认就是缓存的前缀
//失效模式 删除category区下【所有】的数据
@CacheEvict(value = "category", allEntries = true)
//同时进行多种缓存操作 组合删除
// @Caching(evict = {
// //失效模式:修改删除缓存
// @CacheEvict(value = "category", key = "'getLevel1Catrgorys'"),
// @CacheEvict(value = "category", key = "'getCatalogJson'")
// })
//双写模式 修改后再放入缓存
// @CachePut
@Transactional
//级联更新 所有数据
@Override
public void updateCascade(CategoryEntity category) {
this.updateById(category);
categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
//同时修改缓存中的数据
//redis.del(catalogJSON);等待下次主动查询进行更新
}