文档地址: https://docs.spring.io/spring-framework/docs/5.2.22.RELEASE/spring-framework-reference/integration.html#cache
Spring 从 3.1开始定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager
接口来统一不同的缓存技术,并支持使用 JCache (JSR-107)注解简化我们开发;
Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
Cache 接口下Spring 提供了各种xxxCache.的实现﹔如RedisCache,EhCacheCache ,ConcurrentMapCache 等
每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
使用Spring缓存抽象时我们需要关注以下两点
整合SpringCache简化缓存开发步骤:
spring-boot-starter-cache
、spring-boot-starter-data-redis
主要注解操作:
1、引入依赖spring-boot-starter-cache
、spring-boot-starter-data-redis
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
2、写配置,路径:product模块下的application.properties
spring.cache.type=redis
3、在启动类上开启缓存注解@EnableCaching,路径:com/atguigu/gulimall/product/GulimallProductApplication.java
4、修改获取一级分类方法,路径:com/atguigu/gulimall/product/service/impl/CategoryServiceImpl.java
@Cacheable("category") // 代表当前方法的结果需要缓存,如果缓存中有,方法不调用,如果缓存中没有则会调用方法
@Override
public List<CategoryEntity> getLevel_1_Categorys() {
System.out.println("获取一级缓存方法执行");
List<CategoryEntity> categoryEntityList = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
return categoryEntityList;
}
5、启动product服务测试,多次访问http://localhost:10000/,查看控制台打印信息和redis缓存。
SpEL表达式文档:https://docs.spring.io/spring-framework/docs/5.2.22.RELEASE/spring-framework-reference/integration.html#cache-spel-context
默认的行为:
自定义:
1、指定生成key
// @Cacheable(value = {"category"}, key = "#root.method.name")
@Cacheable(value = {"category"}, key = "'getLevel_1_Categorys'") // 代表当前方法的结果需要缓存,如果缓存中有,方法不调用,如果缓存中没有则会调用方法
@Override
public List<CategoryEntity> getLevel_1_Categorys() {
System.out.println("获取一级缓存方法执行");
List<CategoryEntity> categoryEntityList = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
return categoryEntityList;
}
2、指定缓存的ttl
# 单位为毫秒
spring.cache.redis.time-to-live=3600000
原理:
CacheAutoConfiguration -> RedisCacheConfiguration -> 自动配置了RedisCacheManager-> 初始化所有的缓存 -> 每个缓存决定使用什么配置 -> 如果RedisCacheConfiguration有就用,没有就用默认配置 -> 想改缓存的配置,只需要给容器中放一个RedisCacheConfiguration即可 -> 就会应用到当前RedisCacheManager管理的所有缓存分区中
如果自己配置了RedisCacheConfiguration,那么在application.properties里的配置就失效了,需要将application.properties里的配置放到自己配置的RedisCacheConfiguration中开启
RedisCacheConfiguration源码:
private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
ClassLoader classLoader) {
// 如果自己配置了redisCacheConfiguration,就使用自己的,直接返回了,下面的redisProperties不在生效
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;
}
1、编写MyCacheConfig,路径:com/atguigu/gulimall/product/config/MyCacheConfig.java
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching
public class MyCacheConfig {
@Bean
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;
}
}
2、修改redis-cache配置,路径:application.properties
spring.cache.type=redis
# 单位为毫秒
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
@CacheEvict:失效模式
@Caching
@CacheEvict(value = "category", allEntries = true)
spring.cache.redis.key-prefix
的配置1、修改getCatalogJson
方法。路径:com/atguigu/gulimall/product/service/impl/CategoryServiceImpl.java
@Cacheable(value = "category", key = "#root.methodName")
@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {
System.out.println("查询了数据库......");
// 查询出表pms_category所有的记录实体
List<CategoryEntity> categoryEntityList = baseMapper.selectList(null);
// 查出所有的一次分类
List<CategoryEntity> level_1_categorys = getParent_cid(categoryEntityList, 0L);
// 封装数据,构造一个以1级id为键,2级分类列表为值的map
Map<String, List<Catelog2Vo>> collect = level_1_categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), l1 -> {
// 根据一级分类id查找二级分类
List<CategoryEntity> level_2_categorys = getParent_cid(categoryEntityList, l1.getCatId());
// 封装结果为Catelog2Vo的集合
List<Catelog2Vo> catelog2Vos = null;
if (level_2_categorys != null) {
// 把 level_2_categorys 封装为 catelog2Vos
catelog2Vos = level_2_categorys.stream().map(l2 -> {
Catelog2Vo catelog2Vo = new Catelog2Vo(l1.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
// 根据二级分类id查找三级分类
List<CategoryEntity> level_3_categorys = getParent_cid(categoryEntityList, l2.getCatId());
// 将 level_3_categorys 封装为 catelog3Vos
if (level_3_categorys != null) {
List<Catelog2Vo.Catelog3Vo> catelog3Vos = level_3_categorys.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 catelog2Vos;
}));
return collect;
}
2、修改updateDetail
方法,添加@CacheEvict注解。路径:com/atguigu/gulimall/product/service/impl/CategoryServiceImpl.java
// @Caching(evict = {
// @CacheEvict(value = "category", key = "'getLevel_1_Categorys'"),
// @CacheEvict(value = "category", key = "'getCatalogJson'")
// })
@CacheEvict(value = "category", allEntries = true)
@Transactional
@Override
public void updateDetail(CategoryEntity category) {
this.updateById(category);
categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
}
3、启动服务进行测试
读模式:
ache-null-values=true
@Cacheable
上添加sync = true
spring.cache.redis.time-to-live
写模式:
总结: