Spring声明式基于注解的缓存(2-实践篇)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 目录一、序言二、使用示例1、配置(1) application.properties(2) 基于Redis缓存的CacheManager配置2、注解运用测试用例(1) 指定key条件式缓存(2) 返回值为Optional类型条件式缓存(3) 不指定key条件式缓存(4) 指定key删除缓存(5) 指定key更新缓存三、结语

目录

一、序言

二、使用示例

1、配置

(1) application.properties

(2) 基于Redis缓存的CacheManager配置

2、注解运用测试用例

(1) 指定key条件式缓存

(2) 返回值为Optional类型条件式缓存

(3) 不指定key条件式缓存

(4) 指定key删除缓存

(5) 指定key更新缓存

三、结语

一、序言


在前面 Spring声明式基于注解的缓存(1-理论篇)一节中我们大致介绍了基于注解的缓存抽象相关理论知识,包括常用注解@Cacheable、@CachePut、@CacheEvict、@Caching和@CacheConfig,还有缓存相关组件CacheManager和CacheResolver的作用。


这篇是实战环节,主要会包含缓存相关注解的应用。

二、使用示例


1、配置

(1) application.properties

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=lyl
spring.redis.database=0
spring.redis.timeout=1000ms
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.max-active=50
spring.redis.lettuce.pool.max-wait=1000ms
spring.redis.lettuce.pool.time-between-eviction-runs=30000ms


(2) 基于Redis缓存的CacheManager配置

@EnableCaching
@Configuration
public class RedisCacheConfig {
  private static final String KEY_SEPERATOR = ":";
  /**
   * 自定义CacheManager,具体配置参考{@link org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration}
   * @param redisConnectionFactory 自动配置会注入
   * @return
   */
  @Bean(name = "redisCacheManager")
  public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
    RedisSerializer<String> keySerializer = new StringRedisSerializer();
    RedisSerializer<Object> valueSerializer = new GenericJackson2JsonRedisSerializer();
    RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
      .serializeKeysWith(SerializationPair.fromSerializer(keySerializer))
      .serializeValuesWith(SerializationPair.fromSerializer(valueSerializer))
      .computePrefixWith(key -> key.concat(KEY_SEPERATOR));
    return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(cacheConfig).build();
  }
}


2、注解运用测试用例

SpringCacheService定义了相关的缓存操作,如下:

@Service
@CacheConfig(cacheManager = "redisCacheManager")
public class SpringCacheService {
  /**
   * key:缓存key名称,支持SPEL
   * value:缓存名称
   * condition:满足条件可缓存才缓存结果,支持SPEL
   * unless:满足条件结果不缓存,支持SPEL
   * @param stuNo
   * @return
   */
  @Cacheable(key = "#stuNo", value = "student-cache", condition = "#stuNo gt 0", unless = "#result eq null")
  public StudentDO getStudentByNo(int stuNo) {
    StudentDO student = new StudentDO(stuNo, "liuyalou");
    System.out.println("模拟从数据库中读取:" + student);
    return student;
  }
  /**
   * 不指定key,默认会用{@link org.springframework.cache.interceptor.SimpleKeyGenerator}
   * 如果方法无参数则返回空字符串,只有一个参数则返回参数值,两个参数则返回包含两参数的SimpleKey
   * @param username
   * @param age
   * @return
   */
  @Cacheable(value = "user-cache", unless = "#result eq null")
  public UserDO getUserByUsernameAndAge(String username, int age) {
    UserDO userDo = new UserDO(username, age);
    System.out.println("模拟从数据库中读取:" + userDo);
    return userDo;
  }
  @Cacheable(key = "#stuNo + '_' +#stuName", value = "student-cache", unless = "#result?.stuName eq null")
  public Optional<StudentDO> getStudentByNoAndName(int stuNo, String stuName) {
    if (stuNo <= 0) {
      return Optional.empty();
    }
    StudentDO student = new StudentDO(stuNo, stuName);
    System.out.println("模拟从数据库中读取:" + student);
    return Optional.ofNullable(student);
  }
  @CacheEvict(value = "student-cache", key = "#stuNo")
  public void removeStudentByStudNo(int stuNo) {
    System.out.println("从数据库删除数据,key为" + stuNo + "的缓存将会被删");
  }
  @CachePut(value = "student-cache", key = "#student.stuNo", condition = "#result ne null")
  public StudentDO updateStudent(StudentDO student) {
    System.out.println("数据库进行了更新,检查缓存是否一致");
    return student;
  }
}

1) 指定key条件式缓存

这里我们定义了名为student-cache,key为1的缓存,以及是否缓存的两个条件:

  • 如果stuNo小于0则不缓存。
  • 如果方法执行结果不为空才缓存。
/**
   * key:缓存key名称,支持SPEL
   * value:缓存名称
   * condition:满足条件可缓存才缓存结果,支持SPEL
   * unless:满足条件结果不缓存,支持SPEL
   * @param stuNo
   * @return
   */
  @Cacheable(key = "#stuNo", value = "student-cache", condition = "#stuNo gt 0", unless = "#result eq null")
  public StudentDO getStudentByNo(int stuNo) {
    StudentDO student = new StudentDO(stuNo, "liuyalou");
    System.out.println("模拟从数据库中读取:" + student);
    return student;
  }


  @Test
  public void getStudentByNo() {
    StudentDO studentDo = springCacheService.getStudentByNo(1);
    System.out.println(studentDo);
  }

控制台输出如下,如果Redis中没有该student-cache:1对应的值,则会执行方法体的代码。

模拟从数据库中读取:StudentDO[stuName=liuyalou,stuNo=1]
程序执行结果为: StudentDO[stuName=liuyalou,stuNo=1]

该方法执行后,让我们看看Redis中的key,可以看到多了student-cache:1的缓存键值对信息。b6e6ed10ee8c4f77b3b310a1f71feff9.png备注:当再次执行该方法时,不会执行方法体逻辑,而是从Redis中获取对应缓存key的值。

(2) 返回值为Optional类型条件式缓存

这里我们自定义了名为student-cache,key为stuNo_stuName的缓存,方法返回参数为Optional类型。如果Optional的值为空,则方法的执行结果不会被缓存。

  @Cacheable(key = "#stuNo + '_' +#stuName", value = "student-cache", unless = "#result?.stuName eq null")
  public Optional<StudentDO> getStudentByNoAndName(int stuNo, String stuName) {
    if (stuNo <= 0) {
      return Optional.empty();
    }
    StudentDO student = new StudentDO(stuNo, stuName);
    System.out.println("模拟从数据库中读取:" + student);
    return Optional.ofNullable(student);
  }
  @Test
  public void getStudentByNoAndName() {
    Optional<StudentDO> studentDo = springCacheService.getStudentByNoAndName(1, "Nick");
    System.out.println("程序执行结果为: " + studentDo.orElse(null));
  }


备注:#result指向的不是Optional实例,而是Student实例,因为Optional中的值可能为null,这里我们用了安全导航操作符?

控制台输出:

模拟从数据库中读取:StudentDO[stuName=Nick,stuNo=1]
程序执行结果为: StudentDO[stuName=Nick,stuNo=1]


f3a1e2118dae42b0b0fbf93157885ef7.png

(3) 不指定key条件式缓存

下面的方法我们没有指定key属性,key的生成会用默认的key生成器SimpleKeyGenerator来生成。

@Cacheable(value = "user-cache", unless = "#result eq null")
  public UserDO getUserByUsernameAndAge(String username, int age) {
    UserDO userDo = new UserDO(username, age);
    System.out.println("模拟从数据库中读取:" + userDo);
    return userDo;
  }
@Test
  public void getUserByUsernameAndAge() {
    UserDO userDo = springCacheService.getUserByUsernameAndAge("liuyalou", 23);
    System.out.println("程序执行结果为: " + userDo);
  }

314f501cae0e49749f4d37178362d68c.png备注:可以看到SimpleKeyGernerator生成的key名是根据对象属性来生成的。

(4) 指定key删除缓存

这个注解我们用来根据指定缓存key来清除缓存。

  @CacheEvict(value = "student-cache", key = "#stuNo")
  public void removeStudentByStudNo(int stuNo) {
    System.out.println("从数据库删除数据,key为" + stuNo + "的缓存将会被删");
  }
  @Test
  public void getStudentByNo() {
    StudentDO studentDo = springCacheService.getStudentByNo(1);
    System.out.println("程序执行结果为: " + studentDo);
  }
  @Test
  public void removeStudentByStudNo() {
    springCacheService.removeStudentByStudNo(1);
  }

我们先执行getStudentByNo测试用例,再执行removeStudentByStudNo,控制台输出如下:

模拟从数据库中读取:StudentDO[stuName=liuyalou,stuNo=1]
程序执行结果为: StudentDO[stuName=liuyalou,stuNo=1]
从数据库删除数据,key为1的缓存将会被删

备注:执行完后可以看到Redis中的key会被删除。

(5) 指定key更新缓存

接下来我们根据指定key更新缓存,这里我们也指定了缓存条件,只有当缓存结果不为空时才缓存

  @CachePut(value = "student-cache", key = "#student.stuNo", unless = "#result eq null”)
  public StudentDO updateStudent(StudentDO student) {
    System.out.println("数据库进行了更新,检查缓存是否一致");
    return student;
  }


  @Test
  public void updateStudent() {
    StudentDO oldStudent = springCacheService.getStudentByNo(1);
    System.out.println("原缓存内容为:" + oldStudent);
    springCacheService.updateStudent(new StudentDO(1, "Evy"));
    StudentDO newStudent = springCacheService.getStudentByNo(1);
    System.out.println("更新后缓存内容为:" + newStudent);
  }


控制台输出为:

原缓存内容为:StudentDO[stuName=Evy,stuNo=1]
数据库进行了更新,检查缓存是否一致
更新后缓存内容为:StudentDO[stuName=Evy,stuNo=1]


最终Redis中的key信息如下7603062adb154d29859b866894d39270.png

三、结语


总得来说,声明式缓存抽象和声明式事务一样,使用起来都比较简单。更多的细节描述可以参考:Spring缓存抽象官方文档

有同学可能会发现,Spring提供的这些注解不支持过期时间的设置,官方文档也有一些解释,如下:66e0efe046ae4bb4a18f59cce5b6766b.png官方提供的建议是通过缓存提供器来实现,其实就是我们可以通过自定义CacheManager来实现。缓存抽象只是一种逻辑抽象,而不是具体的缓存实现,具体怎么写缓存,缓存写到哪里应该由缓存管理器来实现。


下一节我们会通过自定义CacheResolver、RedisCacheManager、以及相关Cache注解来实现带过期时间的缓存实现。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
15天前
|
XML Java 数据格式
Spring Core核心类库的功能与应用实践分析
【12月更文挑战第1天】大家好,今天我们来聊聊Spring Core这个强大的核心类库。Spring Core作为Spring框架的基础,提供了控制反转(IOC)和依赖注入(DI)等核心功能,以及企业级功能,如JNDI和定时任务等。通过本文,我们将从概述、功能点、背景、业务点、底层原理等多个方面深入剖析Spring Core,并通过多个Java示例展示其应用实践,同时指出对应实践的优缺点。
40 14
|
11天前
|
缓存 Java 数据库连接
Spring框架中的事件机制:深入理解与实践
Spring框架是一个广泛使用的Java企业级应用框架,提供了依赖注入、面向切面编程(AOP)、事务管理、Web应用程序开发等一系列功能。在Spring框架中,事件机制是一种重要的通信方式,它允许不同组件之间进行松耦合的通信,提高了应用程序的可维护性和可扩展性。本文将深入探讨Spring框架中的事件机制,包括不同类型的事件、底层原理、应用实践以及优缺点。
43 8
|
16天前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
59 5
|
13天前
|
XML 前端开发 安全
Spring MVC:深入理解与应用实践
Spring MVC是Spring框架提供的一个用于构建Web应用程序的Model-View-Controller(MVC)实现。它通过分离业务逻辑、数据、显示来组织代码,使得Web应用程序的开发变得更加简洁和高效。本文将从概述、功能点、背景、业务点、底层原理等多个方面深入剖析Spring MVC,并通过多个Java示例展示其应用实践,同时指出对应实践的优缺点。
41 2
|
15天前
|
缓存 NoSQL Java
Spring Boot中的分布式缓存方案
Spring Boot提供了简便的方式来集成和使用分布式缓存。通过Redis和Memcached等缓存方案,可以显著提升应用的性能和扩展性。合理配置和优化缓存策略,可以有效避免常见的缓存问题,保证系统的稳定性和高效运行。
32 3
|
17天前
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
34 4
|
17天前
|
安全 Java 数据安全/隐私保护
如何使用Spring Boot进行表单登录身份验证:从基础到实践
如何使用Spring Boot进行表单登录身份验证:从基础到实践
33 5
|
17天前
|
监控 Java 数据安全/隐私保护
如何用Spring Boot实现拦截器:从入门到实践
如何用Spring Boot实现拦截器:从入门到实践
38 5
|
19天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
58 2
|
19天前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
40 2