从 0 学习 Spring 缓存数据

前言

昨天在开发业务时,打算加入缓存层来提高系统响应速度。查找了一些资料,发现 Spring 的缓存功能十分强大!只需要添加少量的代码,就可以轻松缓存方法所返回的对象。这篇文章通过描述一个实际使用例子,介绍 Spring Cache 的使用限制以及注意事项。

环境准备

  • Redis 5+
  • JDK 1.8+
  • Gradle 6+
  • 一款你喜爱的 IDE

实践过程

添加依赖

打开 build.gradle 文件,添加 Spring Cache 依赖。

implementation 'org.springframework.boot:spring-boot-starter-cache' implementation 'org.springframework.boot:spring-boot-starter-data-redis' 

创建模型

@Data @AllArgsConstructor public class Post implements Serializable {      private Long id;      private String title;      private String content; } 

PS:这里使用到了 Lombok 插件,如果不熟悉可先查询相关资料进行了解。

创建模型仓库

public interface PostRepository {      Post getById(Long id);  } 
@Component public class PostRepositoryImpl implements PostRepository {     @Override     public Post getById(Long id) {         // 模拟查询时间         simulateSlowService();         return new Post(100L, "title", "content");     }      private void simulateSlowService() {         try {             Long time = 3000L;             Thread.sleep(time);         } catch (InterruptedException e) {             e.printStackTrace();         }     } } 

编写控制器

@RestController public class PostController {      private final PostRepository postRepository;      public PostController(PostRepository postRepository) {         this.postRepository = postRepository;     }      @GetMapping("posts/{id}")     public Post getPostById(@PathVariable("id") Long id) {         return postRepository.getById(id);     } } 

针对一些不容易被修改的资源,如果每次都需要到持久化数据库中进行查询,无疑是十分浪费的,体验也差,下面我们使用 Spring Cache 来改进一波。

使用 Spring Cache

@EnableCaching @SpringBootApplication public class CacheApplication {     public static void main(String[] args) {         SpringApplication.run(CacheApplication.class, args);     } } 

添加 @EnableCaching 注解启动 Spring Cache。

spring:   cache:     type: redis   redis:     host: 127.0.0.1     port: 6379 

这里用 Redis 作为缓存引擎,如果小伙伴想用其他引擎,可自行查阅文档进行配置。

@RestController public class PostController {      private final PostRepository postRepository;      public PostController(PostRepository postRepository) {         this.postRepository = postRepository;     }      @Cacheable(cacheNames = "getPostById", key = "#id")     @GetMapping("posts/{id}")     public Post getPostById(@PathVariable("id") Long id) {         return postRepository.getById(id);     } } 

使用 @Cacheable 注解 getPostById 方法,使用了 cacheNames 和 key 参数。这里先不展开说,下面会集中梳理几种注解以及它们的参数意义。

Spring Cache 注解

Spring Cache 常用的 5 个注解,分别是:

  • @EnableCaching
  • @Cacheable
  • @CachePut
  • @CacheEvict
  • @CacheConfig

@EnableCaching

在启动添加 @EnableCaching 注解让系统开启缓存功能。

@Cacheable

功能是开启缓存,可以标记在类上或者是方法上。在调用方法时,会先从缓存中获取结果,若不存在再执行方法。主要参数包括 cacheNames、key、condition 和 unless 等。

  • cacheNames:用于指定缓存存储的集合名,必须填写。
  • key:缓存对象存储在集合中的 key 值,缺省按照函数的所有参数组合作为 key 值。
  • condition:缓存对象的条件,需使用 SpEL 表达式。只有满足表达式条件的内容才会被缓存。
  • unless:缓存对象的条件,需使用 SpEL 表达式。它不同于 condition 参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对返回对象进行判断。
  • keyGenerator:用于指定 key 生成器。若需要自定义 key 生成器,需要实现 KeyGenerator 接口,并使用该参数来指定。
  • cacheManager:用于指定使用哪个缓存管理器。
  • cacheResolver:用于指定使用那个缓存解析器。

@CachePut

针对方法配置,与 @Cacheable 不同的地方在于它每次都会触发真实方法的调用。简单来说就是更新缓存数据。主要参数和 @Cacheable 一致。

@CacheEvict

针对方法配置,用来从缓存中移除相应数据。除了与 @Cacheable 相同的参数以外,还有 allEntries 和 beforeInvocation。

  • allEntries 非必须,默认为 false。当为 true 时,会移除所有数据。
  • beforeInvocation 非必须,默认为 false,会在调用方法之后移除数据。当为 true 时,会在调用方法之前移除数据。

@CacheConfig

该注解是一个类级注解,可以让类下面的方法共享 cacheNames、keyGenerator、cacheManager 和 cacheResolver 参数。

自定义 cacheNames

这里是为了让我们的缓存注解支持自定义 TTL 失效时间,类似下面这种效果。

// 3600 秒后缓存集合自动过期 @Cacheable(cacheNames = "getPostById#3600", key = "#id") 

为了实现这种效果,我们创建一个 CustomRedisCacheManager 自定义类,如下所示。

public class CustomRedisCacheManager extends RedisCacheManager {     public CustomRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {         super(cacheWriter, defaultCacheConfiguration);     }      @Override     protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {          String[] array = StringUtils.delimitedListToStringArray(name, "#");          name = array[0];          if (array.length > 1) {             long ttl = Long.parseLong(array[1]);             cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl));         }          return super.createRedisCache(name, cacheConfig);     } } 

使用自定义 CustomRedisCacheManager 配置 CacheConfig。

public class CacheConfig extends CachingConfigurerSupport {      @Value("${spring.redis.host}")     private String redisHost;      @Value("${spring.redis.port}")     private Integer redisPort;      @Value("${spring.redis.database}")     private Integer redisDatabase;      @Override     @Bean     public CacheManager cacheManager() {         RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()                 .entryTtl(Duration.ofDays(1))                 .computePrefixWith(cacheName -> "caching:" + cacheName);          return new CustomRedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory()), defaultCacheConfig);     }      @Bean     public RedisConnectionFactory redisConnectionFactory() {         RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();         configuration.setHostName(redisHost);         configuration.setPort(redisPort);         configuration.setDatabase(redisDatabase);         return new LettuceConnectionFactory(configuration);     } } 

总结

本文主要介绍了 Spring Cache 的基本使用方式和常用注解。后续文章准备深入了解其底层原理。

您可能还会对下面的文章感兴趣: