1、简介
- Spring 从 3.1 开始定义了 org.springframework.cache.Cache
和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术;并支持使用 JCache(JSR-107)注解简化我们开发; - Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合;Cache 接 口 下 Spring 提 供 了 各 种 xxxCache 的 实 现 ; 如 RedisCache ,EhCacheCache, ConcurrentMapCache 等;
- 每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
- 使用 Spring 缓存抽象时我们需要关注以下两点;
- 1、确定方法需要被缓存以及他们的缓存策略
- 2、从缓存中读取之前缓存存储的数据
2、基础概念
3、简单使用
3.1、引入依赖
<!--引入redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!--排除spring redis自己整合的lettuce-core-->
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--上面排除了lettuce-core后 使用jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
3.2、在启动类上开启缓存
@SpringBootApplication
@MapperScan("com.qinjian.gulimall.product.dao")
@EnableCaching
public class GulimallProductApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallProductApplication.class, args);
}
}
3.3、在方法上使用注解
@Cacheable(value = {"category"},key = "#root.method.name") // 使用SpEl表达式获取方法名
@Override
public List<CategoryEntity> getLevel1Categorys() {
System.out.println("getLevel1Categorys......");
long l = System.currentTimeMillis();
List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
return categoryEntities;
}
4、进阶配置
SpringBoot自动配置了Spring Cache,在CacheAutoConfiguration中导入了RedisCacheConfiguration
RedisCacheConfiguration自动配置了RedisCacheManager,RedisCacheManager初始化所有的缓存,每个缓存决定使用什么配置,如果redisCacheConfiguration有就用已有的,没有就用默认配置,想改缓存的配置,就只需要给容器中放一个redisCacheConfiguration即可,就会应用到当前RedisCacheManager管理的所有缓存分区中
创建一个缓存配置类,自定义一个RedisCacheConfiguration
@Configuration
@EnableCaching // 把启动类中的该注解移到此处统一配置
@EnableConfigurationProperties(CacheProperties.class) // 加载原有的配置
public class MyCacheConfig {
/**
* 配置文件中的东西没有用上
* 原来的CacheProperties
* @ConfigurationProperties(prefix = "spring.cache")
* public class CacheProperties{}
* 要想让它生效,使用注解
* @return
*/
@Bean
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// 此处必须重新返回config对象,不然序列化设置将不生效
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.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;
}
}
yml配置cache
spring:
redis:
host: 192.168.200.139
port: 6379
# spring cache使用redis作为缓存
cache:
# 设置缓存类型为redis
type: redis
redis:
# 设置缓存存活时间 单位是毫秒
time-to-live: 3600000
# 设置缓存前缀 如果指定了前缀就用我们指定的前缀,如果没有就默认使用缓存的名字作为前缀
key-prefix: CACHE_
# 设置是否使用缓存前缀,默认为true
use-key-prefix: false
# 是否缓存空值,防止缓存穿透
cache-null-values: true
5、注解
5.1、@Cacheable
@Cacheable(value = {“category”},key = “‘level1Categorys’”):(读模式)代表当前方法的结果需要缓存,如果缓存中有,方法不调用,如果缓存中没有,会调用方法,最后将方法的结果放入缓存
- 1、value = {“category”}:每一个需要缓存的数据,都来指定要放到哪个名字的缓存。【缓存的分区(按照业务类型分)】
- 2、默认行为
- 1、如果缓存中有,方法不用调用
- 2、key默认自动生成:缓存的名字category::SimpleKey [](自主生成的key值)
- 3、缓存的value的值。默认使用jdk序列化机制,将序列化后的数据存到redis
- 4、默认过期时间ttl -1;永久
- 3、自定义:
- 1、指定生成的缓存使用的key: key属性指定,可以使用字符串(必须用单引号包裹),也可接受一个SpEl表达式
SpEl的详细语法:https://docs.spring.io/spring-framework/docs/5.3.25/reference/html/integration.html#cache-spel-context - 2、指定缓存的数据的存活时间ttl:yml配置文件中修改ttl
- 3、将数据保存为json格式:上面的自定义配置
- 1、指定生成的缓存使用的key: key属性指定,可以使用字符串(必须用单引号包裹),也可接受一个SpEl表达式
// @Cacheable(value = {"category"},key = "'level1Categorys'") // 指定key的字符串名称
@Cacheable(value = {"category"},key = "#root.method.name") // 使用SpEl表达式获取方法名
@Override
public List<CategoryEntity> getLevel1Categorys() {
System.out.println("getLevel1Categorys......");
long l = System.currentTimeMillis();
List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
return categoryEntities;
}
5.2、@CacheEvict
@CacheEvict(value = “category”,key = “‘getLevel1Categorys’”):缓存失效模式,执行完方法后,删除指定的缓存。
- value: 指定分区名称
- key:指定要删除的缓存名称
- allEntries:是否要删除指定分区下的所有缓存
// @CacheEvict(value = "category",key = "'getLevel1Categorys'")
@CacheEvict(value = "category",allEntries = true)
@Transactional
@Override
public void updateDetail(CategoryEntity category) {
this.updateById(category);
if (!StringUtils.isEmpty(category.getName())) {
categoryBrandRelationService.upDateCategory(category.getCatId(), category.getName());
// todo 更新其他关联表
}
// 删除缓存
// stringRedisTemplate.delete("catalogJsons");
}
5.3、@Caching
@Caching:可以使用多个缓存注解组合使用
@Caching(evict = {
@CacheEvict(value = "category",key = "'getLevel1Categorys'"),
@CacheEvict(value = "category",key = "'getCatalogJson'")
})
@Transactional
@Override
public void updateDetail(CategoryEntity category) {
this.updateById(category);
if (!StringUtils.isEmpty(category.getName())) {
categoryBrandRelationService.upDateCategory(category.getCatId(), category.getName());
// todo 更新其他关联表
}
// 删除缓存
// stringRedisTemplate.delete("catalogJsons");
}
5、表达式语法
6、原理与不足
- 1、读模式:
- 缓存穿透:查询一个null数据。解决:缓存空数据; yml配置 cache-null-values: true
- 缓存击穿:大量并发进来同时查询一个正好过期的数据。解决:加锁,默认是无加锁的。
@Cacheable中有一个sync=true的属性,设置为true时会加锁同步执行,解决击穿问题。 - 缓存雪崩:缓存中大量的key同时过期。解决:加随机时间。加上过期时间yml配置 time-to-live: 3600000
- 2、写模式:(保证缓存与数据库一致)
- 1、读写加锁。(读多写少的业务数据)
- 2、引入Canal,感知MySql的更新去更新数据库
- 3、读多写多,直接取数据库查询就行
总结:
常规数据:(读多写少,即时性、一致性不高的数据),完全可以使用Spring-Cache。写模式(只要缓存的数据有过期时间就足够了)
特殊数据:特殊设计