餐掌柜之——SpringCache的基本使用

SpringCache概述

缓存的框架太多了,各有各的优势,比如Redis、Memcached、Guava、Caffeine等等。

如果我们的程序想要使用缓存,就要与这些框架耦合。聪明的架构师已经在利用接口来降低耦合了,利用面向对象的抽象和多态的特性,做到业务代码与具体的框架分离。

但我们仍然需要显式地在代码中去调用与缓存有关的接口和方法,在合适的时候插入数据到缓存里,在合适的时候从缓存中读取数据。想一想「AOP」的适用场景,这不就是天生就应该AOP去做的吗?

自Spring 3.1起,提供了类似于 @Transactional 注解事务的注解Cache支持,且提供了Cache抽象,在此之前一般通过AOP实现。

使用Spring Cache的好处:

  • 提供基本的Cache抽象,方便切换各种底层Cache;
  • 通过注解Cache可以实现类似于事务一样,缓存逻辑透明的应用到我们的业务代码上,且只需要更少的代码就可以完成;
  • 提供事务回滚时也自动回滚缓存;
  • 支持比较复杂的缓存逻辑;

2.1、SpringCache概述及核心配置

是的,Spring Cache就是一个这个框架。它利用了AOP,实现了基于注解的缓存功能,并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能了。而且Spring Cache也提供了很多默认的配置,用户可以3秒钟就使用上一个很不错的缓存功能。

那我们怎么使用这个插件呢?来我们看下我们项目中集成的framework-starter-redis的模块:

1
2
|——restkeeper-framework 	核心组件模块,主要框架集成:mybatis-plus、seata、jwt、redis等等
|———— framework-starter-redis关于redis及SpringCache基础集成

framework-starter-redis模块中我们需要导入对SpringCache的支持:

1
2
3
4
5
<!--springboot的cache支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

下面我们看下framework-starter-redis模块中的RedisCacheConfig配置类是如何实现上述功能的:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.itheima.restkeeper.config;

import org.springframework.cache.CacheManager;
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.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;


/**
* @ClassName RedisCacheConfig.java
* @Description redis配置
*/
@Configuration
//开启caching的支持
@EnableCaching
public class RedisCacheConfig {


/**
* 申明缓存管理器,会创建一个切面(aspect)并触发Spring缓存注解的切点(pointcut)
* 根据类或者方法所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,
* 将数据添加到缓存之中或者从缓存中移除某个值
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 配置序列化(解决乱码的问题)
//对key的序列化操作:String
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
//对value的序列化操作:json
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer =
new GenericJackson2JsonRedisSerializer();

//配置config,指定超时时间记得key val 序列化处理
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(60))
//配置key的序列化方式
.serializeKeysWith(RedisSerializationContext
.SerializationPair
.fromSerializer(redisSerializer))
//配置value的序列化方式
.serializeValuesWith(RedisSerializationContext
.SerializationPair
.fromSerializer(genericJackson2JsonRedisSerializer))
//关闭空值的存储
.disableCachingNullValues()
.computePrefixWith(cacheName -> cacheName + ":");

//设置特有的Redis配置
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
//定制化的Cache 设置过期时间
cacheConfigurations.put("dataDictList",customRedisCacheConfiguration(config,Duration.ofSeconds(6000)));
cacheConfigurations.put("affixs",customRedisCacheConfiguration(config,Duration.ofSeconds(3000)));
cacheConfigurations.put("brands",customRedisCacheConfiguration(config,Duration.ofSeconds(300)));

//使用建造者进行初始化
return RedisCacheManager.builder(redisConnectionFactory)
.transactionAware() //Cache的事务支持
.withInitialCacheConfigurations(cacheConfigurations)
.cacheDefaults(config)
.build();
}

/**
* 设置RedisConfiguration配置
* @param config
* @param ttl
* @return
*/
public RedisCacheConfiguration customRedisCacheConfiguration(RedisCacheConfiguration config, Duration ttl) {
return config.entryTtl(ttl); //设置缓存缺省超时时间
}
}

2.2、SpringCache的项目集成

以model-basic-producer项目为例,单我们使用SpringCache的时候只需要依赖: framework-starter-redis模块

image-20210710233212254

然后在model-basic-producer的application.yml中添加redis配置即可==【如果使用nacos配置中心,需要在配置中心中添加配置】==:

1
2
3
4
5
6
7
8
9
10
11
#spring相关配置
spring:
main:
allow-bean-definition-overriding: true
redis:
redisson:
config: classpath:singleServerConfig.yaml
#redis配置信息
host: 192.168.200.129
port: 6379
password: pass

第二章、优雅使用SpringCache

1、缓存层选择

image-20210710235405378

选择producer的Face的理由:

1、producer的face是dubbo服务发生产者,其功能都是单一职能

2、service的功能过于细腻,切关联甚广

3、controller层功能过于粗狂,不易缓存的维护

2、SpringCache注解详解

2.1、@Cacheable注解

==如果缓存中没有:查询数据库,存储缓存,返回结果,==

==如果缓存中有:直接返回结果==

​ 作用:可以用来进行缓存的写入,将结果存储在缓存中,以便于在后续调用的时候可以直接返回缓存中的值,而不必再执行实际的方法。 最简单的使用方式,注解名称=缓存名称,使用例子如下:

@Cacheable

2.2、@CacheEvict注解

@CacheEvict:删除缓存的注解,这对删除旧的数据和无用的数据是非常有用的。这里还多了一个参数(allEntries),设置allEntries=true时,可以对整个条目进行批量删除

@CacheEvict

2.3、@CachePut注解

@CachePut:当需要更新缓存而不干扰方法的运行时 ,可以使用该注解。也就是说,始终执行该方法,并将结果放入缓存

备注:

@Cacheable 的逻辑是:查找缓存 - 有就返回 -没有就执行方法体 - 将结果缓存起来;

@CachePut 的逻辑是:执行方法体 - 将结果缓存起来;

@CachePut

2.4、@Caching注释

在使用缓存的时候,有可能会同时进行更新和删除,会出现同时使用多个注解的情况.而@Caching可以实现

1
2
3
//添加user缓存的同时,移除userPage的缓存
@Caching(put =@CachePut(value = "user",key ="#userVo.id"),
evict = @CacheEvict(value = "userPage",allEntries = true))

提供的SpEL上下文数据:

Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:

img

2.5、小结

对于缓存声明,spring的缓存提供了一组java注解:

  • @Cacheable
    • 功能:触发缓存写入,如果缓存中没有,查询数据库,存储缓存,返回结果,如果缓存中有,直接返回结果
    • 应用:查询数据库方法
  • @CacheEvict
    • 功能:触发缓存清除
    • 应用:删除或修改数据库方法
  • @CachePut
    • 功能:更新缓存(不会影响到方法的运行)。
    • 应用:新增到数据库方法
  • @Caching
    • 功能:重新组合要应用于方法的多个缓存操作
    • 应用:上面的注解的组合使用