0%

【Spring】SpringCache缓存框架

SpringCache缓存框架简介与使用


1 简介

  • Spring从3.1开始,定义了CacheCacheManager接口来统一管理不同的缓存
  • 通过注解的形式,来实现对缓存的读取,写入,删除操作,简化开发
  • SpringCache文档
注解 说明
@Cacheable 读取缓存。缓存存在,不执行方法直接返回缓存;若缓存不存在,执行方法,并将方法的返回值写入缓存
@CacheEvict 删除缓存
@CachePut 将方法的返回值写入缓存
@Caching 组合多个缓存注解操作

2 操作演示

  • 操作演示使用的是redis作为缓存,且使用springboot来演示,非spring

2.1 环境准备

  • (1)依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!-- 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>
    </dependency>
  • (2)yaml配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    spring:
    cache:
    type: redis # 缓存类型:redis
    redis:
    time-to-live: 30000 # 所有redis-key过期时间(单位:毫秒)
    cache-null-values: true # 缓存null值(解决缓存穿透)
    redis:
    host: 127.0.0.1
    port: 6379
  • (3)SpringBoot启动类添加@EnableCaching


2.2 @Cacheable

1
2
3
4
5
@Cacheable(value = {#Category}, key = "#keyName", sync = #true/false)

value: 缓存分类or目录
key: 缓存的key名(使用SpEl表达式)
sync: 是否加本地锁(虽然不是分布式锁,但也是能降低缓存击穿问题)
1
2
3
4
5
6
7
// 【演示代码】
@Cacheable(value = "test", key = "'msg'", sync = true)
@Override
public String getMsg() {
System.out.println("log --- 调用了数据库,没命中缓存");
return "Hello World";
}

2.3 @CacheEvict | @CachePut

  • @CacheEvict | @CachePut 两注解使用场景基本一致,用于维护缓存和数据库一致性
    • @CacheEvict:先更新数据库,后删除缓存
    • @CachePut::先更新数据库,再更新缓存
1
2
3
4
5
@CacheEvict(value = {#category}, key = "#keyName", allEntries = #true/false)

value:缓存目录
key:缓存key
allEntries:是否删除满足条件所有缓存(可以只填value值,不填具体的key值,就会将value目录下的所有缓存删除)
1
2
3
4
@CachePut(value = {#category}, key = "#keyName")

value:缓存目录
key:缓存key
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 【演示代码】
// 移除缓存(更新删除)
@CacheEvict(value = "test", key = "'msg'")
@Override
public void updateMsg() {
// 更新数据库逻辑
}

// ---

// 更新缓存(双写模式)
@CachePut(value = "test", key = "'msg'")
@Override
public String updateMsg() {
// 更新数据库逻辑,并将更新后的数据返回
}

2.4 @Caching

1
2
3
4
5
@Caching(cacheable = {}, put = {}, evict = {})

cachable:放入@Cachable注解
put:放入@CachePut注解
evict:放入@CacheEvict注解
1
2
3
4
5
6
7
8
9
10
11
12
// 【演示代码】
// 组合操作
@Caching(
evict = {
@CacheEvict(value = "test", key = "'msg1'"),
@CacheEvict(value = "test", key = "'msg2'")
}
)
@Override
public void updateMsg2() {
// 更新数据库数据
}

3 自定义配置类

  • 因为缓存存入redis中,value使用了jdk序列化的形式,不便于跨平台使用,建议使用json来序列化
    • 因此需要自定一个配置类来解决问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@EnableConfigurationProperties(CacheProperties.class) // 使yaml中配置绑定的配置类放入ioc容器中
@EnableCaching
@Configuration
public class MyRedisCacheConfig {

@Bean
public RedisCacheConfiguration getRedisCacheConfiguration(CacheProperties cacheProperties) {
// 修改value序列化方法,默认使用jdk序列化,改成json序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// 其余照搬源码(org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration.createConfiguration()方法)
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}

4 SpringCache的不足

  • 从缓存三大问题分析
  • 缓存穿透:配置中有spring.cache.redis.cache-null-values = true,来存储null,可以完美解决缓存穿透问题
  • 缓存击穿@Cacheable(sync = true),读取缓存可以使用本地同步锁,解决方法虽不如分布式锁,但一定程度上也算是解决缓存击穿问题
  • 缓存雪崩:无法解决,所有缓存设置的过期时间都一致,有可能存在缓存雪崩问题