这篇文章主要讲述如何在 Springboot 中集成 Redis Cache 功能。
概述
在 Springboot 项目中引入 Redis Cache 包含以下三个步骤:
- 安装部署 Redis;
- 引入相关依赖;
- 开启 Cache 功能,并使用 @Cacheable 、@CachePut 、@CacheEvict 、@Caching 等 annotaion 标注需要使用 Cache 的方法;
第一个步骤可以参考其它文章,我们重点介绍后面两个步骤。
引入依赖
主要是引入两个依赖,分别是 Spring Cache 和 Redis 相关的依赖。
1 2 3 4 5 6 7 8 9 10 11
| <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> </dependency>
|
开启 Cache
首先在引入缓存的方法上添加相关的 annotaion, 如下代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Service public class UserService {
@Autowired private UserMapper userMapper;
@Cacheable(value = "users", key = "#id") public User getUser(Integer id) {
User user = userMapper.getUser(id);
return user; }
}
|
最后使用 @EnableCaching 开启缓存功能即可。
基本原理
Cache Anotation
在 Spring Caceh 中,有两个关键的接口:org.springframework.cache.Cache
和 org.springframework.cache.CacheManager
. 两个接口用来统一不同的缓存技术,Cache 接口是对缓存的抽象,它包含了基本的操作,如增加、删除、读取等,CacheManager 是管理 Cache 的接口,通过它可以获取具体的 Cache 实现类。不同的缓存技术需要实现这两个接口,并添加到 Spring 容器中,由 Spring 统一管理。data-redis 便实现了这两个接口。
Spring Cache 本质是在使用缓存的方法上加入一个过滤器,根据方法的输入参数和输出结果生成一个缓存的 Key, 并将返回结果缓存到指定的 Key 上。 怎么标注要使用缓存的方法呢? 在 Spring 中引入了如下的 annotation, 它们主要是应用在方法上。
- @Cacheable: 主要是用在查询方法上,用于将返回结果缓存到 Cache 中,该 annotation 在方法调用前先判断缓存是否存在,如果存在则直接返回缓存的值;如果缓存不存在,则执行方法,并将返回结果缓存到 Cache 中;
- @CachePut:用于将返回结果缓存到 Cache 中;
- @CacheEvict:用于清空指定的缓存,可以通过 beforeInvocation 参数控制执行的时间,若为 true, 则在方法前执行,反之方法后执行;
- @Caching:该注解可以实现同一个方法上同时使用多种注解,如 Cacheable, CachePut, CacheEvict;
Cache 参数配置:
- @CacheConfig:用于配置 Cache 相关参数,如:cacheNames, keyGenerator, cacheManager 和 cacheResolver, 一般配置在类上;
- @EnableCaching:开启缓存功能,一般放在启动类上;
在方法上添加了 @Cacheable, @CachePut, @CacheEvict 和 @Caching annotaion 之后,Spring 使用 Aop 技术,在方法中会添加一个 CacheInterceptor
过滤器,其核心代码如下:
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
| private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT);
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
List<CachePutRequest> cachePutRequests = new LinkedList<>(); if (cacheHit == null) { collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests); }
Object cacheValue; Object returnValue;
if (cacheHit != null && !hasCachePut(contexts)) { cacheValue = cacheHit.get(); returnValue = wrapCacheValue(method, cacheValue); } else { returnValue = invokeOperation(invoker); cacheValue = unwrapReturnValue(returnValue); }
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
for (CachePutRequest cachePutRequest : cachePutRequests) { cachePutRequest.apply(cacheValue); }
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
return returnValue; }
|
主要流程包括:
- 判断是否有 CacheEvict annotation, 若有 CacheEvict,且设置 beforeInvocation 为 true, 则在方法前执行 CacheEvict 操作;
- 判断是否有 Cacheable annotation, 若有 Cacheable,则直接从 Cache 中读取内容:1) 若缓存命中(存在)且没有 CachePut 操作,则将缓存内容包装成返回结果; 2) 若缓存没有命中,则调用方法,并包装返回结果;
- 收集 cachePut 操作, 如果有 cachePut 或 Cacheable 未命中,则执行写入缓存操作;
- 最后判断是否有 CacheEvict, 若有 CacheEvict,且设置 beforeInvocation 为 false, 则在方法后执行 CacheEvict 操作。
说明:使用 @Cacheable, @CachePut annotaion, 方法签名中必须有返回结果,否则将存储 NULL 对象。
加载 CacheInterceptor
CacheInterceptor 是实现缓存存储逻辑的关键,在启动类加上 @EnableCaching annotaion,通过 CachingConfigurationSelector 类引入 ProxyCachingConfiguration Configuration 类. 而 CacheInterceptor 就是由 ProxyCachingConfiguration 定义生成的。
Cache, CacheManager 是 Spring 定义的 SPI 接口,通过引入 spring-boot-starter-data-redis 依赖,将 RedisCache, RedisCacheManager 加入到项目中,最后结合 Springboot 的 autoconfigure 技术将这两个实现类加入容器中,从而被 CacheInterceptor 引用到。
代码实例
以代码为例,使用 mybatis 作为 ORM 框架,使用 Redis 作为缓存的后端实现,版本约定如下:
1 2 3 4 5 6 7 8
| <properties> <spring-boot.version>2.2.5.RELEASE</spring-boot.version> <mysql.driver.version>8.0.16</mysql.driver.version> <mybatis-spring-boot.version>1.3.5</mybatis-spring-boot.version>
</properties>
|
定义 User Bean
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class User implements Serializable {
private Integer id; private String name; private String password; private String sex; private String nickname;
public User() { }
}
|
id 字段为用户表主键,在插入时需要从数据库返回用于缓存的 Key.
定义 User Mapper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public interface UserMapper {
@Select("SELECT * FROM t_user_info") List<User> getAll();
@Select("SELECT * FROM t_user_info WHERE id = #{id}") User getUser(Integer id);
@Insert("INSERT INTO t_user_info(name,password,sex,nickname) VALUES(#{name}, #{password}, #{sex}, #{nickname})") @Options(useGeneratedKeys = true, keyProperty = "id") void insert(User user);
@Update("UPDATE t_user_info SET name=#{name},nickname=#{nickname} WHERE id =#{id}") void update(User user);
@Delete("DELETE FROM t_user_info WHERE id =#{id}") void delete(Integer id);
}
|
说明:在执行 insert 操作时,使用 @Options(useGeneratedKeys = true, keyProperty = "id")
返回自增主键。
扫描 Mapper
在启动类中加入 @MapperScan("org.noahsark.cache.mapper")
, 指定扫描的 Mapper 对象。
定义 User Service
在 Service 类中加入对缓存的操作,如下所示:
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
| @Service public class UserService {
@Autowired private UserMapper userMapper;
public List<User> getUsers() { List<User> users = userMapper.getAll(); return users; }
@Cacheable(value = "users", key = "#id") public User getUser(Integer id) {
User user = userMapper.getUser(id);
return user; }
@CachePut(value = "users", key = "#user.id") public User save(User user) { userMapper.insert(user);
return user; }
@CachePut(value = "users", key = "#user.id") public User update(User user) { userMapper.update(user);
return user; }
@CacheEvict(value = "users", key = "#id") public void delete(Integer id) { userMapper.delete(id); } }
|
说明:
- @Cacheable,@CachePut annotation 的方法签名必须有返回结果;
- Redis 使用 String 格式存储数据,完整的 key 为:users::#id, id 为具体的值;
key 参数使用 spEL 表达式,可以包含以下参数:
开启缓存
1 2 3 4 5 6 7 8 9
| @SpringBootApplication @MapperScan("org.noahsark.cache.mapper") @EnableCaching public class SpringbootCacheApp {
public static void main(String[] args) { SpringApplication.run(SpringbootCacheApp.class, args); } }
|
在启动类中加入 @EnableCaching.
工程代码:https://github.com/noahsarkzhang-ts/springboot-lab/tree/main/springcloud-cache
参考:
1. Spring Boot 2.X(七):Spring Cache 使用
2. SpringBoot 缓存之 @Cacheable 详细介绍