Mall商城的高级篇的开发(四)Spring Cache
官方文档地址:https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#cache
简介
从 3.1 版开始,Spring 框架支持透明地向现有 Spring 应用程序添加缓存。与事务支持类似,缓存抽象允许一致使用各种缓存解决方案,而对代码的影响最小。
在 Spring Framework 4.1 中,缓存抽象显着扩展,支持 JSR-107 注释和更多自定义选项。
理解缓存抽象
缓存与缓冲区
“缓冲区”和“缓存”这两个术语往往可以互换使用。但是请注意,它们代表不同的事物。传统上,缓冲区用作快速实体和慢速实体之间的数据的中间临时存储。由于一方必须等待另一方(这会影响性能),缓冲区通过允许整个数据块一次移动而不是小块移动来缓解这种情况。数据仅从缓冲区写入和读取一次。此外,缓冲区对于知道它的至少一方是可见的。
另一方面,根据定义,缓存是隐藏的,并且任何一方都不知道发生了缓存。它还提高了性能,但通过让相同的数据以快速方式多次读取来实现。
您可以在此处找到有关缓冲区和缓存之间差异的进一步说明。
缓存抽象的核心是将缓存应用于 Java 方法,从而根据缓存中可用的信息减少执行次数。也就是说,每次调用目标方法时,抽象都会应用缓存行为来检查是否已经为给定参数调用了该方法。如果已调用,则返回缓存的结果,而无需调用实际方法。如果没有调用该方法,则调用该方法,并将结果缓存并返回给用户,以便下次调用该方法时,返回缓存的结果。这样,对于给定的一组参数,昂贵的方法(无论是 CPU 还是 IO 绑定)只能被调用一次,并且结果可以重用,而不必再次实际调用该方法。缓存逻辑是透明应用的,对调用者没有任何干扰
要使用缓存抽象,您需要注意两个方面:
缓存声明:标识需要缓存的方法及其策略。
缓存配置:存储数据和从中读取数据的后备缓存。
缓存基本概念
CacheManager
Spring Cache 只是提供一种缓存的规则。
整合Spring Cache简化缓存开发
- 导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <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>
- 配置
//自动配置类帮助我们导入了哪些配置 1. CacheAutoConfigurtion 会帮助我们导入 RedisCacheConfigurtion的配置。 2. RedisCacheConfigurtion配置会自动在我们的spring容器中,自动装备了RedisCacheManager缓存管理器,我们只需要拿来用就行。
spring.cache.type=redis
- 使用
基于声明性注解的缓存:
@Cacheable
:触发缓存填充。@CacheEvict
:触发缓存移除。@CachePut
:在不干扰方法执行的情况下更新缓存。@Caching
:重新组合多个缓存操作以应用于一个方法。组合对缓存的操作(填充和删除)@CacheConfig
:在类级别共享一些常见的缓存相关设置。/** * @Cacheable() 需要指定我们的缓存数据放到哪里(缓存分区(按照业务的类型区分)) * 就好像Spring-cache是我们的陕西省,@Cacheable({"xian_cache"}),我们的缓存数据要放到西安。 */ @Cacheable({"catalog"}) //代表当前方法返回结果需要被缓存,如果缓存中有,就不缓存,如果没有,就缓存 @Override public List<CategoryEntity> getLevel_one() { long l = System.currentTimeMillis(); List<CategoryEntity> categoryEntityList = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0)); System.out.println("消耗时间:" + (System.currentTimeMillis() - l)); return categoryEntityList; }
缓存的默认行为:
- 如果缓存中有,不会去查询数据库
- key是默认生成,
缓存的名称::Simplekey []
- value 是进过java序列化之后的结果
- 默认过期时间,-1,用不过期
自定义缓存
- 自定义key,key属性指定,接受一个SpEL表达式
https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#cache-annotations-cacheable-key
- 自定义缓存的过期时间,在配置文件中修改,
spring.cache.redis.time-to-live=3600000 ms
- 自定义序列化,以JSON格式保存数据
CacheAutoConfiguration
–>RedisCacheConfiguration
–>自动配置了RedisCacheManager
–>初始化所有的缓存-每个缓存决定使用什么配置–>如果redisCacheconfiguration
有就用己有的,没有就用默认配罝。想改缓存的配置,只需要给我们的spring容器中放一个实例,就会应用到当前的
RedisCacheManager
管理的缓存分区中
package com.uin.product.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableCaching
@EnableConfigurationProperties(CacheProperties.class) //开启属性配置的绑定的功能
public class MyCacheConfig {
@Autowired
CacheProperties cacheProperties;
/**
* 在配置文件中配置cache的缓存过期时间没生效
*/
@Bean
RedisCacheConfiguration redisCacheConfiguration() {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//cacheConfiguration.entryTtl();
// 自定义key的序列化
config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
// 自定义value的序列化
config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// 去配置文件获取缓存的过期时间 配置配置文件中的过期时间生效
CacheProperties.Redis redis = cacheProperties.getRedis();
if (redis.getTimeToLive() != null) {
config = config.entryTtl(redis.getTimeToLive());
}
if (redis.getKeyPrefix() != null) {
config = config.prefixKeysWith(redis.getKeyPrefix());
}
if (redis.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (redis.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
优缺点
在使用缓存的时候,我们需要对缓存进行分析:
- 读模式
会出现
- 缓存穿透
查询一个null数据。在SpringCache中的配置文件,可以开启缓存null值。
- 缓存击穿
大量的并发同时查询正好过期的数据,由于我们缓存的数据过期了,大量的请求都落到我们的数据库。
解决方案:加锁。
在SpringCache,中默认是无加锁的,所以在解决缓存击穿的问题还是需要另找别的办法。
- 缓存雪崩
大量的key同时过期。解决:加随机时间。SpringCache中可以加上过期时间
- 写模式
为了保证缓存和数据库的数据一致。
- 读写加锁。实用与读多写少的场景
- 引入Cannel,感知数据库的数据是否更新。
- 读多写多,直接去数据库查询
常规数据的(读多写少的,及时性,一致性要求不高的数据);完全可以使用Spring-Cache。写模式使用缓存的过期时间就足够解决问题了。
特殊数据,针对业务场景具体设计。