目录

  • 一、序言
  • 二、使用示例
    • 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,还有缓存相关组件CacheManagerCacheResolver的作用。

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


二、使用示例

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();}
}

备注:上面我们指定了 keyvalue的序列化器,还有缓存key的拼接策略。

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;}
 @Testpublic 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的缓存键值对信息。

备注:当再次执行该方法时,不会执行方法体逻辑,而是从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);}
 @Testpublic 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]

让我们再看下Redis中的key情况:

(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;}
@Testpublic void getUserByUsernameAndAge() {UserDO userDo = springCacheService.getUserByUsernameAndAge("liuyalou", 23);System.out.println("程序执行结果为: " + userDo);}

方法执行完后,让我们看看Redis中的key情况:

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

(4) 指定key删除缓存

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

 @CacheEvict(value = "student-cache", key = "#stuNo")public void removeStudentByStudNo(int stuNo) {System.out.println("从数据库删除数据,key为" + stuNo + "的缓存将会被删");}
 @Testpublic void getStudentByNo() {StudentDO studentDo = springCacheService.getStudentByNo(1);System.out.println("程序执行结果为: " + studentDo);}@Testpublic 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;}
 @Testpublic 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信息如下:


三、结语

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

有同学可能会发现,Spring提供的这些注解不支持过期时间的设置,官方文档也有一些解释,如下:


官方提供的建议是通过缓存提供器来实现,其实就是我们可以通过自定义CacheManager来实现。缓存抽象只是一种逻辑抽象,而不是具体的缓存实现,具体怎么写缓存,缓存写到哪里应该由缓存管理器来实现。

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

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

  1. spring事物管理--声明式(AspectJ)注解实现 (推荐使用)

    1.表结构及数据 2.使用的jar包 3.service.Dao层接口与实现类: Dao接口: //转账案例持久层接口 public interface AccountDao {/*** @param ...

  2. spring注解驱动开发-7 Spring声明式事务

    Spring 声明式事务 前言 @EnableTransactionManagement AutoProxyRegistrar InfrastructureAdvisorAutoProxyCreato ...

  3. Spring 声明式事务应该怎么学?

    1.引言 Spring 的声明式事务极大地方便了日常的事务相关代码编写,它的设计如此巧妙,以至于在使用中几乎感觉不到它的存在,只需要优雅地加一个 @Transactional 注解,一切就都顺理成章地 ...

  4. 【Spring学习笔记 九】Spring声明式事务管理实现机制

    什么是事务?事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用,关乎数据准确性的地方我们一定要用到事务,防止业务逻辑出错. 什么是事务管理,事务管理对于企业应用而言至 ...

  5. spring声明式事务

    11.声明式事务 11.1 事务回顾 把一组业务当做一个业务来坐,要么都成功,要么都失败! 事物在项目开发中的重要性不言而喻,关系到数据的一致性文件 确保完整性和一致性 事务的ACID原则 原子性(A ...

  6. Spring 声明式事务在业务开发中容易碰到的坑总结

    Spring 声明式事务,在业务开发使用上可能遇到的三类坑,包括: 第一,因为配置不正确,导致方法上的事务没生效.我们务必确认调用 @Transactional 注解标记的方法是 public 的,并 ...

  7. 缓存初解(五)---SpringMVC基于注解的缓存配置--web应用实例

    之前为大家介绍了如何使用spring注解来进行缓存配置 (EHCache 和 OSCache)的简单的例子,详见 Spring基于注解的缓存配置--EHCache AND OSCache 现在介绍一下 ...

  8. Spring声明式事务管理、事务的传播行为xml配置

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 1. <tx:method name="insert*" propagat ...

  9. 为什么spring中的controller跳转出错_你的业务代码中Spring声明式事务处理正确了吗?

    Spring 针对 Java Transaction API (JTA).JDBC.Hibernate 和 Java Persistence API(JPA) 等事务 API,实现了一致的编程模型,而 ...

最新文章

  1. 用 namspace 隔离 DHCP 服务 - 每天5分钟玩转 OpenStack(90)
  2. jQuery 要点总结
  3. view.ondraw
  4. Quartus II12.0安装教程
  5. sqlyog软件的使用
  6. Java基础之String深入解析
  7. 网络爬虫--27.csv文件的读取和写入
  8. 鲲鹏基础软件开发赛道openLooKeng赛题火热报名中,数十万大奖等您来收割
  9. 计算机操作系统笔记(三)
  10. 201521123014《Java程序设计》第1周学习总结
  11. Android简易实战教程--第十四话《模仿金山助手创建桌面Widget小部件》
  12. penuppendown在python中是啥意思_pen down是什么意思
  13. 汽车级485通信电路
  14. 超详细的 Wireshark 使用教程
  15. spring AOP依赖配置大全
  16. 计算机打字比赛活动策划书怎么写,打字比赛策划书范文
  17. 捷通华声联合清华海峡研究院 打造中国顶尖人工智能研究中心
  18. 建立您的初创企业:通过URL邀请他人
  19. linux点亮硬盘灯命令 简书,1.4linux 命令-文件、磁盘管理
  20. C语言实现大小写转换,如果输入的不是字母就重新输入

热门文章

  1. SQL 多表查询去除重复
  2. swoole 安装和简单实用
  3. MIUI开发版和稳定版有什么区别
  4. setInterval影响性能导致卡死的解决方法
  5. Java F-bounded
  6. 三种近场通信技术特点与未来展望
  7. FreeType粗体研究
  8. 计算机择业方向,计算机就业的几个方向
  9. 极路由的“802.1x手机号登陆wifi”插件,电脑连接wifi方法
  10. cxf3.2 wsdl2java异常_cxf工具wsdl2java生成客户端代码错误,,急,遇到过的给看看麻烦各位大神了...