Spring声明式基于注解的缓存(2-实践篇)
目录
- 一、序言
- 二、使用示例
- 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();}
}
备注:上面我们指定了
key
和value
的序列化器,还有缓存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
来实现。缓存抽象只是一种逻辑抽象,而不是具体的缓存实现,具体怎么写缓存,缓存写到哪里应该由缓存管理器来实现。
下一节我们会通过自定义CacheResolver
、RedisCacheManager
、以及相关Cache注解来实现带过期时间的缓存实现。
Spring声明式基于注解的缓存(2-实践篇)相关推荐
- spring事物管理--声明式(AspectJ)注解实现 (推荐使用)
1.表结构及数据 2.使用的jar包 3.service.Dao层接口与实现类: Dao接口: //转账案例持久层接口 public interface AccountDao {/*** @param ...
- spring注解驱动开发-7 Spring声明式事务
Spring 声明式事务 前言 @EnableTransactionManagement AutoProxyRegistrar InfrastructureAdvisorAutoProxyCreato ...
- Spring 声明式事务应该怎么学?
1.引言 Spring 的声明式事务极大地方便了日常的事务相关代码编写,它的设计如此巧妙,以至于在使用中几乎感觉不到它的存在,只需要优雅地加一个 @Transactional 注解,一切就都顺理成章地 ...
- 【Spring学习笔记 九】Spring声明式事务管理实现机制
什么是事务?事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用,关乎数据准确性的地方我们一定要用到事务,防止业务逻辑出错. 什么是事务管理,事务管理对于企业应用而言至 ...
- spring声明式事务
11.声明式事务 11.1 事务回顾 把一组业务当做一个业务来坐,要么都成功,要么都失败! 事物在项目开发中的重要性不言而喻,关系到数据的一致性文件 确保完整性和一致性 事务的ACID原则 原子性(A ...
- Spring 声明式事务在业务开发中容易碰到的坑总结
Spring 声明式事务,在业务开发使用上可能遇到的三类坑,包括: 第一,因为配置不正确,导致方法上的事务没生效.我们务必确认调用 @Transactional 注解标记的方法是 public 的,并 ...
- 缓存初解(五)---SpringMVC基于注解的缓存配置--web应用实例
之前为大家介绍了如何使用spring注解来进行缓存配置 (EHCache 和 OSCache)的简单的例子,详见 Spring基于注解的缓存配置--EHCache AND OSCache 现在介绍一下 ...
- Spring声明式事务管理、事务的传播行为xml配置
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 1. <tx:method name="insert*" propagat ...
- 为什么spring中的controller跳转出错_你的业务代码中Spring声明式事务处理正确了吗?
Spring 针对 Java Transaction API (JTA).JDBC.Hibernate 和 Java Persistence API(JPA) 等事务 API,实现了一致的编程模型,而 ...
最新文章
- 用 namspace 隔离 DHCP 服务 - 每天5分钟玩转 OpenStack(90)
- jQuery 要点总结
- view.ondraw
- Quartus II12.0安装教程
- sqlyog软件的使用
- Java基础之String深入解析
- 网络爬虫--27.csv文件的读取和写入
- 鲲鹏基础软件开发赛道openLooKeng赛题火热报名中,数十万大奖等您来收割
- 计算机操作系统笔记(三)
- 201521123014《Java程序设计》第1周学习总结
- Android简易实战教程--第十四话《模仿金山助手创建桌面Widget小部件》
- penuppendown在python中是啥意思_pen down是什么意思
- 汽车级485通信电路
- 超详细的 Wireshark 使用教程
- spring AOP依赖配置大全
- 计算机打字比赛活动策划书怎么写,打字比赛策划书范文
- 捷通华声联合清华海峡研究院 打造中国顶尖人工智能研究中心
- 建立您的初创企业:通过URL邀请他人
- linux点亮硬盘灯命令 简书,1.4linux 命令-文件、磁盘管理
- C语言实现大小写转换,如果输入的不是字母就重新输入
热门文章
- SQL 多表查询去除重复
- swoole 安装和简单实用
- MIUI开发版和稳定版有什么区别
- setInterval影响性能导致卡死的解决方法
- Java F-bounded
- 三种近场通信技术特点与未来展望
- FreeType粗体研究
- 计算机择业方向,计算机就业的几个方向
- 极路由的“802.1x手机号登陆wifi”插件,电脑连接wifi方法
- cxf3.2 wsdl2java异常_cxf工具wsdl2java生成客户端代码错误,,急,遇到过的给看看麻烦各位大神了...