引入依赖
<!-- spring cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- redis -->
<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>
<!-- jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
写配置
spring:
redis:
host: 192.168.56.10
port: 6379
password: 123456
cache:
type: redis
测试使用缓存
// 触发将数据保存到缓存的操作
@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、启动类添加注解:@EnableCaching
2、方法添加注解:@Cacheable
/**
* 每一个缓存的数据我们都来指定放到哪个名字的缓存【缓存的分区(按照业务类型分)】
*
* 代表当前方法的结果需要缓存,如果缓存中有,方法不用调用。
* 如果缓存中没有,会调用方法,最后将方法的结果放入缓存
* @return
*/
@Cacheable({"catagory"})
@Override
public List<CategoryEntity> getLevel1Categorys() {
System.out.println("getLevel1Categorys......");
long l = System.currentTimeMillis();
return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
}
3、默认行为
- 如果缓存中有,方法不用调用
- key默认自动生成,缓存名字::SimpleKey [](自动生成的key)
- 缓存的value值,默认使用的是jdk的序列化机制,将序列化后的值存在redis中
- 默认时间ttl=-1
4、自定义属性
- 指定生成缓存的key:key属性指定接受spEL,@Cacheable(value={缓存名},key="‘key’"),由于spEL动态取值,所以需要再添加一个单引号,表达式语法可参考:https://docs.spring.io/spring-framework/docs/5.3.3-SNAPSHOT/reference/html/integration.html#cache-spel-context
- 指定缓存数据的存活时间:配置文件:spring.cache.redis.time-to-live: 300(单位毫秒)
- 将数据保存为json格式:
- 原理:
- CacheAutoConfiguration -> RedisCacheConfiguration -> 自动配置了RedisCacheManager -> 初始化所有的缓存 -> 每个缓存决定使用什么配置 -> 如果redisCacheConfiguration有就用已有的,没有就用默认配置 -> 想改缓存的配置,只需要给容器中放一个RedisCachcConfiguration即可 -> 就会应用到当前RcdisCacheManager管理的所有缓存分区中
- 配置缓存信息:
- 原理:
spring.cache.type=redis
# spring.cache.cache-names=
spring.cache.redis.time-to-live=3600000
# 如果使用前缀,就用我们指定的前缀,如果没有就默认使用缓存的名字作为前缀
spring.cache.redis.key-prefix=CACHE_
spring.cache.redis.use-key-prefix=true
# 是否缓存空值。防止缓存穿透
spring.cache.redis.cache-null-values=true
-
- 自定义缓存配置:
// 开启属性配置绑定
@EnableConfigurationProperties({
CacheProperties.class
}
)
@EnableCaching
@Configuration
public class MyCacheConfig {
/**
* 配置文件中的东西没有用上
*1、原来和配置文件绑定的配置类,是这样子的
* @ConfigurationProperties(prefix = "spring.cache")
* public class CacheProperties
*2、要让他生效,要用这个注解,
* @return
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
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;
}
}
级联数据的更新
1、失效模式:@CacheEvict
/**
* @CacheEvict:失效模式
* @param category
*/
@CacheEvict(value = {"catagory"}, key="'getLevel1Categorys'")
@Transactional(rollbackFor = Exception.class)
@Override
public void updateCascade(CategoryEntity category) {
this.updateById(category);
categoryBrandRelationDao.updateCategory(category.getCatId(), category.getName());
//同时修改缓存中的数据,
//redis.del('catalogJSON'),等待下次查询时更新
}
同时进行多种缓存操作:@Caching
@Caching(evict={
@CacheEvict(value = {"catagory"}, key = "'getLevel1Categorys'"),
@CacheEvict(value = {"catagory"}, key = "'getCatalogJson'")
})
@Transactional(rollbackFor = Exception.class)
@Override
public void updateCascade(CategoryEntity category) {
// xxxx
}
或者指定删除某个分区下的所有数据@cacheEvict(value = “category” ,allEntries = true),存储同一类型的数据,都可以指定成同一个分区。分区名默认就是缓存的前缀:
@CacheEvict(value = {"catagory"}, allEntries = true)
2、双写模式:@CachePut
原理与不足
-
读模式
- 缓存穿透:查询一个null数据;解决:spring.cache.redis.cache-null-values=true
- 缓存击穿:大量并发进来查询一个正好过期的数据;解决:加锁:sync = true(该注解只能添加到@Cacheable且是本地锁)
- 缓存雪崩:大量的key同时过期;解决:加随机时间:spring.cache.redis.time-to-live=3600000
-
写模式
- 读写加锁
- 引入canal,感知到mysql的更新去更新redis
- 读多写多,直接查询数据库
-
原理
- CacheManager(RedisCacheManager) -> cache(RedisCache) -> cache负责缓存的读写