第 4-4 课:Spring Boot 中使⽤ Cache 缓存的使⽤

我们知道绝⼤多数的⽹站 / 系统,最先遇到的⼀个性能瓶颈就是数据库,使⽤缓存做数据库的前置缓存,可以
⾮常有效地降低数据库的压⼒,从⽽提升整个系统的响应效率和并发量。
 
以往使⽤缓存时,通常创建好缓存⼯具类,使⽤时将对应的⼯具类注⼊,操作⼯具类在前端处理缓存的逻
辑。其实这种⽅式是低效的,⼤部分使⽤缓存的场景是基于数据库的缓存,这类缓存场景的逻辑往往是:如
果缓存中存在数据,就从缓存中读取,如果缓存中不存在数据或者数据失效,就再从数据库中读取。
 
为了实现这样的逻辑,往往需要在业务代码中写很多的逻辑判断,那么有没有通⽤的代码来实现这样的逻辑
呢?其实有,按照这个逻辑我们可以写⼀个⼯具类来实现,每次需要这样判断逻辑时调⽤⼯具类中的⽅法即
可,还有没有更优雅的使⽤⽅式呢?答案是肯定的,如果我们把这种固定的逻辑使⽤ Java 注解来实现,每
次需要使⽤时只需要在对应的⽅法或者类上写上注解即可。
 
Spring 也看到了这样的使⽤场景,于是有了 注释驱动的 Spring Cache 。它的原理是 Spring Cache 利⽤了
Spring AOP 的动态代理技术,在项⽬启动的时候动态⽣成它的代理类,在代理类中实现了对应的逻辑。
Spring Cache 是在 Spring 3.1 中引⼊的基于注释( Annotation )的缓存( Cache )技术,它本质上不是⼀个
具体的缓存实现⽅案,⽽是⼀个对缓存使⽤的抽象,通过在既有代码中添加少量它定义的各种 Annotation
即能够达到缓存⽅法的返回对象的效果。
 
Spring 的缓存技术还具备相当的灵活性 ,不仅能够使⽤ SpEL Spring Expression Language )来定义缓存
key 和各种 condition ,还提供了开箱即⽤的缓存临时存储⽅案,也⽀持和主流的专业缓存如 EHCache
成。
 
SpEL Spring Expression Language )是⼀个⽀持运⾏时查询和操作对象图的强⼤的表达式语⾔,其
语法类似于统⼀ EL ,但提供了额外特性, 显式⽅法调⽤和基本字符串模板函数
 
其特点总结如下:
通过少量的配置 Annotation 注释即可使得既有代码⽀持缓存;
  • ⽀持开箱即⽤ Out-Of-The-Box,即不⽤安装和部署额外第三⽅组件即可使⽤缓存;
  • ⽀持 Spring Express Language,能使⽤对象的任何属性或者⽅法来定义缓存的 key condition
  • ⽀持 AspectJ,并通过其实现任何⽅法的缓存⽀持;
  • ⽀持⾃定义 key 和⾃定义缓存管理者,具有相当的灵活性和扩展性。

Spring Boot Cache 的使⽤

Spring Boot 提供了⾮常简单的解决⽅案,这⾥给⼤家演示最核⼼的三个注解: @Cacheable
@CacheEvict @CachePut spring-boot-starter-cache Spring Boot 体系内提供使⽤ Spring Cache
Starter 包。
在开始使⽤这三个注解之前,来介绍⼀个新的组件 spring-boot-starter-cache
 
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
spring-boot-starter-cache Spring Boot 提供缓存⽀持的 starter 包,其会进⾏缓存的⾃动化配置和识别,
Spring Boot Redis ⾃动配置了 RedisCacheConfifiguration 等信息, spring-boot-starter-cache 中的注解也
主要是使⽤了 Spring Cache 提供的⽀持。

@Cacheable

@Cacheable ⽤来声明⽅法是可缓存的,将结果存储到缓存中以便后续使⽤相同参数调⽤时不需执⾏实际的
⽅法,直接从缓存中取值。 @Cacheable 可以标记在⼀个⽅法上,也可以标记在⼀个类上。当标记在⼀个⽅
法上时表示该⽅法是⽀持缓存的,当标记在⼀个类上时则表示该类所有的⽅法都是⽀持缓存的。
我们先来⼀个最简单的例⼦体验⼀下:
@RequestMapping("/hello")
@Cacheable(value="helloCache")
public String hello(String name) {
 System.out.println("没有⾛缓存!");
 return "hello "+name;
}
来测试⼀下,启动项⽬后访问⽹址 http://localhost:8080/hello?name=neo ,输出:没有⾛缓存!,再次访问
⽹址 http://localhost:8080/hello?name=neo ,输出栏没有变化,说明这次没有⾛ hello() 这个⽅法,内容直接
由缓存返回。
@Cacheable(value="helloCache") 这个注释的意思是,当调⽤这个⽅法时,会从⼀个名叫 helloCache 的缓
存中查询,如果没有,则执⾏实际的⽅法(也可是查询数据库),并将执⾏的结果存⼊缓存中,否则返回缓
存中的对象。这⾥的缓存中的 key 就是参数 name value 就是返回的 String 值。
 
@Cacheable ⽀持如下⼏个参数。
  • value:缓存的名称。
  • key:缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写;如果不指定,则缺省按照⽅法的所有 参数进⾏组合。
  • condition:触发条件,只有满⾜条件的情况才会加⼊缓存,默认为空,既表示全部都加⼊缓存,⽀持 SpEL
我们把上⾯的⽅法稍微改成这样:
@RequestMapping("/condition")
@Cacheable(value="condition",condition="#name.length() <= 4")
public String condition(String name) {
 System.out.println("没有⾛缓存!");
 return "hello "+name;
}
启动后在浏览器中输⼊⽹址 http://localhost:8080/condition?name=neo ,第⼀次输出栏输出:没有⾛缓存!
再次执⾏⽆输出,表明已经⾛缓存。在浏览器中输⼊⽹址 http://localhost:8080/condition?name=ityouknow
浏览器执⾏多次仍然⼀直输出:没有⾛缓存!说明条件 condition ⽣效。
结合数据库的使⽤来做测试:
@RequestMapping("/getUsers")
@Cacheable(value="usersCache",key="#nickname",condition="#nickname.length() >= 6")
public List<User> getUsers(String nickname) {
 List<User> users=userRepository.findByNickname(nickname);
 System.out.println("执⾏了数据库操作");
 return users;
}
启动后在浏览器中输⼊⽹址 http://localhost:8080/getUsers?nickname=neo
输出栏输出:
Hibernate: select user0_.id as id1_0_, user0_.email as email2_0_, user0_.nickname 
as nickname3_0_, user0_.pass_word as pass_wor4_0_, user0_.reg_time as reg_time5_0_
, user0_.user_name as user_nam6_0_ from user user0_ where user0_.nickname=?
执⾏了数据库操作
多次执⾏,仍然输出上⾯的结果,说明每次请求都执⾏了数据库操作,再输⼊
http://localhost:8080/getUsers?nickname=ityoukonw 进⾏测试。只有第⼀次返回了上⾯的内容,再次执⾏输
出栏没有变化,说明后⾯的请求都已经从缓存中拿取了数据。
 
最后总结⼀下:当执⾏到⼀个被 @Cacheable 注解的⽅法时, Spring ⾸先检查 condition 条件是否满⾜,如
果不满⾜,执⾏⽅法,返回;如果满⾜,在缓存空间中查找使⽤ key 存储的对象,如果找到,将找到的结果
返回,如果没有找到执⾏⽅法,将⽅法的返回值以 key-value 对象的⽅式存⼊缓存中,然后⽅法返回。
需要注意的是当⼀个⽀持缓存的⽅法在对象内部被调⽤时是不会触发缓存功能的。

@CachePut

项⽬运⾏中会对数据库的信息进⾏更新,如果仍然使⽤ @Cacheable 就会导致数据库的信息和缓存的信息不
⼀致。在以往的项⽬中,我们⼀般更新完数据库后,再⼿动删除掉 Redis 中对应的缓存,以保证数据的⼀致
性。 Spring 提供了另外的⼀种解决⽅案,可以让我们以优雅的⽅式去更新缓存。 GitChat
 
@Cacheable 不同的是使⽤ @CachePut 标注的⽅法在执⾏前,不会去检查缓存中是否存在之前执⾏过
的结果,⽽是每次都会执⾏该⽅法,并将执⾏结果以键值对的形式存⼊指定的缓存中。
 
以上⾯的⽅法为例,我们再来做⼀个测试:
@RequestMapping("/getPutUsers")
@CachePut(value="usersCache",key="#nickname")
public List<User> getPutUsers(String nickname) {
 List<User> users=userRepository.findByNickname(nickname);
 System.out.println("执⾏了数据库操作");
 return users;
}
我们新增⼀个 getPutUsers ⽅法, value key 设置和 getUsers ⽅法保持⼀致,使⽤ @CachePut 。同时⼿动
在数据库插⼊⼀条 nikename ityouknow 的⽤户数据。
INSERT INTO `user` VALUES ('1', 'ityouknow@126.com', 'ityouknow', '123456', '2018'
, 'keepSmile');
在浏览器中输⼊⽹址 http://localhost:8080/getUsers?nickname=ityouknow ,并没有返回⽤户昵称为
ityouknow 的⽤户信息,再次输⼊⽹址 http://localhost:8080/getPutUsers?nickname=ityouknow 可以查看到
此⽤户的信息,再次输⼊⽹址 http://localhost:8080/getUsers?nickname=ityouknow 就可以看到⽤户昵称为
ityouknow 的信息了。
 
说明执⾏在⽅法上声明 @CachePut 会⾃动执⾏⽅法,并将结果存⼊缓存。
 
@CachePut 配置⽅法
  • value 缓存的名称。
  • key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照⽅法的所有 参数进⾏组合。
  • condition 缓存的条件,可以为空,使⽤ SpEL 编写,返回 true 或者 false,只有为 true 才进⾏缓存。
可以看出 @CachePut 的参数和使⽤⽅法基本和 @Cacheable ⼀致。
 
@CachePut 也可以标注在类上和⽅法上。
 

@CacheEvict

@CacheEvict 是⽤来标注在需要清除缓存元素的⽅法或类上的,当标记在⼀个类上时表示其中所有的⽅法的
执⾏都会触发缓存的清除操作。 @CacheEvict 可以指定的属性有 value key condition allEntries
beforeInvocation ,其中 value key condition 的语义与 @Cacheable 对应的属性类似。
 
value 表示清除操作是发⽣在哪些 Cache 上的(对应 Cache 的名称); key 表示需要清除的是哪个 key
如未指定则会使⽤默认策略⽣成的 key condition 表示清除操作发⽣的条件。下⾯来介绍⼀下新出现的两个 GitChat
属性 allEntries beforeInvocation
 

allEntries 属性

allEntries boolean 类型,表示是否需要清除缓存中的所有元素,默认为 false ,表示不需要。当指定了
allEntries true 时, Spring Cache 将忽略指定的 key ,有的时候我们需要 Cache ⼀下清除所有的元素,这
⽐⼀个⼀个清除元素更有效率。
 
在上⼀个⽅法中我们使⽤注解: @CachePut(value="usersCache",key="#nickname") 来更新缓
存,但如果不写 key="#nickname" Spring Boot 会以默认的 key 值去更新缓存,导致最上⾯的
getUsers() ⽅法并没有获取最新的数据。但是现在我们使⽤ @CacheEvict 就可以解决这个问题了,它会将所
有以 usersCache 为名的缓存全部清除。我们来看个例⼦:
@RequestMapping("/allEntries")
@CacheEvict(value="usersCache", allEntries=true)
public List<User> allEntries(String nickname) {
 List<User> users=userRepository.findByNickname(nickname);
 System.out.println("执⾏了数据库操作");
 return users;
}
⼿动修改⽤户表的相关信息,⽐如注册时间。在浏览器中输⼊⽹址 http://localhost:8080/getUsers?
nickname=ityouknow 发现缓存中的数据并没有更新,再次访问地址 http://localhost:8080/getUsers?
nickname=ityouknow 会发现数据已经更新,并且输出栏输出 执⾏了数据库操作 ,这表明已经将名为
usersCache 的缓存记录清空了。

beforeInvocation 属性

清除操作默认是在对应⽅法成功执⾏之后触发的,即⽅法如果因为抛出异常⽽未能成功返回时也不会触发清
除操作。使⽤ beforeInvocation 可以改变触发清除操作的时间,当我们指定该属性值为 true 时, Spring 会在
调⽤该⽅法之前清除缓存中的指定元素。
@RequestMapping("/beforeInvocation")
@CacheEvict(value="usersCache", allEntries=true, beforeInvocation=true)
public void beforeInvocation() {
 throw new RuntimeException("test beforeInvocation");
}
我们来做⼀个测试,在⽅法中添加⼀个异常,访问⽹址 http://localhost:8080/beforeInvocation 查看
usersCache 的缓存是否被更新。
 
按照上⾯的实验步骤,⼿动修改⽤户表的相关信息,访问⽹址 http://localhost:8080/getUsers?
nickname=ityouknow 发现缓存中的数据并没有更新;再访问⽹址 http://localhost:8080/beforeInvocation
报错误,先不⽤管这⾥,再次访问地址 http://localhost:8080/getUsers?nickname=ityouknow 会发现数据已
经更新,并且输出栏输出 执⾏了数据库操作 。这表明虽然在测试的过程中⽅法抛出了异常,但缓存中名为 GitChat
usersCache 的记录都已被清空。

总结⼀下其作⽤和配置⽅法

@Cacheable 作⽤和配置⽅法

主要针对⽅法配置,能够根据⽅法的请求参数对其结果进⾏缓存:

主要参数

解释

举例例

 

value

 

缓存的名称,在 spring 配置⽂文件中定义,必须指定⾄至少⼀一个

如 @Cacheable(value="mycache")

或者 @Cacheable(value=

{"cache1","cache2"}

 

key

缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不不指定,则缺省按照⽅方法的所 有参数进⾏行行组合

如@Cacheable(value="testcache", key="#userName")

 

condition

 

缓存的条件,可以为空,使⽤用 SpEL 编写,返回true 或者 false,只有为 true 才进⾏行行缓存

如@Cacheable(value="testcache", condition="#userName.length()>2")

@CachePut 作⽤和配置⽅法

@CachePut 的作⽤ 是主要针对⽅法配置,能够根据⽅法的请求参数对其结果进⾏缓存,和 @Cacheable
同的是,它每次都会触发真实⽅法的调⽤。

主要参数

解释

举例例

 

value

 

缓存的名称,在 spring 配置⽂文件中定义,必须指定⾄至少⼀一个

如 @Cacheable(value="mycache")

或者 @Cacheable(value=

{"cache1","cache2"}

 

key

缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不不指定,则缺省按照⽅方法的所 有参数进⾏行行组合

如@Cacheable(value="testcache", key="#userName")

 

condition

 

缓存的条件,可以为空,使⽤用 SpEL 编写,返回true 或者 false,只有为 true 才进⾏行行缓存

如@Cacheable(value="testcache", condition="#userName.length()>2")

@CacheEvict 作⽤和配置⽅法

主要针对⽅法配置,能够根据⼀定的条件对缓存进⾏清空。

主要参数

解释

举例例

 

value

 

缓存的名称,在 spring 配置⽂文件中定义, 必须指定⾄至少⼀一个

如 @CachEvict(value="mycache")

或者 @CachEvict(value=

{"cache1","cache2"}

 

key

缓存的 key,可以为空,如果指定要按照SpEL 表达式编写,如果不不指定,则缺省按照⽅方法的所有参数进⾏行行组合

如@CachEvict(value="testcache", key="#userName")

 

condition

缓存的条件,可以为空,使⽤用 SpEL 编写,返回 true 或者 false,只有为 true 才清空缓存

如 @CachEvict(value="testcache", condition="#userName.length()>2")

 

allEntries

是否清空所有缓存内容,缺省为 false,如果指定为 true,则⽅方法调⽤用后将⽴立即清空所有缓存

如@CachEvict(value="testcache", allEntries=true)

 

 

beforeInvocation

是否在⽅方法执⾏行行前就清空,缺省为 false, 如果指定为 true,则在⽅方法还没有执⾏行行的时候就清空缓存,缺省情况下,如果⽅方法执⾏行行抛出异常,则不不会清空缓存

 

如@CachEvict(value="testcache", beforeInvocation=true)

@Cacheable @CacheEvict @CachePut 三个注解⾮常灵活,满⾜了我们对数据缓存的绝⼤多数使
⽤场景,并且使⽤起来⾮常的简单⽽⼜强⼤,在实际⼯作中我们可以灵活搭配使⽤。

总结

Spring 提供了基于注释驱动的 Spring Cache ,它是⼀个对缓存使⽤的抽象,将我们常⽤的缓存策略都进⾏了
⾼度抽象,让我们在项⽬中使⽤时只需要添加⼏个注解,即可完成⼤多数缓存策略的实现。 Spring Boot
Starter Cache Spring Boot 提供给我们在 Spring Boot 中使⽤ Spring Cache Starter 包,集成后⽅便在
Spring Boot 体系中使⽤缓存。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值