Spring Boot 中使用 Cache 缓存的使用

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

咱们知道绝大多数的网站/系统,最早遇到的一个性能瓶颈就是数据库,使用缓存作数据库的前置缓存,能够很是有效地下降数据库的压力,从而提高整个系统的响应效率和并发量。前端

以往使用缓存时,一般建立好缓存工具类,使用时将对应的工具类注入,操做工具类在前端处理缓存的逻辑。其实这种方式是低效的,大部分使用缓存的场景是基于数据库的缓存,这类缓存场景的逻辑每每是:若是缓存中存在数据,就从缓存中读取,若是缓存中不存在数据或者数据失效,就再从数据库中读取。git

为了实现这样的逻辑,每每须要在业务代码中写不少的逻辑判断,那么有没有通用的代码来实现这样的逻辑呢?其实有,按照这个逻辑咱们能够写一个工具类来实现,每次须要这样判断逻辑时调用工具类中的方法便可,还有没有更优雅的使用方式呢?答案是确定的,若是咱们把这种固定的逻辑使用 Java 注解来实现,每次须要使用时只须要在对应的方法或者类上写上注解便可。github

Spring 也看到了这样的使用场景,因而有了注释驱动的 Spring Cache。它的原理是 Spring Cache 利用了 Spring AOP 的动态代理技术,在项目启动的时候动态生成它的代理类,在代理类中实现了对应的逻辑。spring

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 和自定义缓存管理者,具备至关的灵活性和扩展性。

1.1    Spring Boot 中 Cache 的使用

Spring Boot 提供了很是简单的解决方案,这里给你们演示最核心的三个注解:@Cacheable、@CacheEvict、@CachePut。spring-boot-starter-cache 是 Spring Boot 体系内提供使用 Spring Cache 的 Starter 包。app

在开始使用这三个注解以前,来介绍一个新的组件 spring-boot-starter-cache。函数

<dependency>
<groupId></groupId>   org.springframework.boot
<artifactId></artifactId>   spring-boot-starter-cache
</dependency>

spring-boot-starter-cache 是 Spring Boot 提供缓存支持的 starter 包,其会进行缓存的自动化配置和识别,Spring Boot 为 Redis 自动配置了 RedisCacheConfiguration 等信息,spring-boot-starter-cache 中的注解也主要是使用了 Spring Cache 提供的支持。

1.1.1  @Cacheable

@Cacheable 用来声明方法是可缓存的,将结果存储到缓存中以便后续使用相同参数调用时不需执行实际的方法,直接从缓存中取值。@Cacheable 能够标记在一个方法上,也能够标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类全部的方法都是支持缓存的。

咱们先来一个最简单的例子体验一下:

"/hello"@RequestMapping()
value"helloCache"@Cacheable(=)
public String hello(String name) {
out"没有走缓存!"    System..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。

咱们把上面的方法稍微改为这样:

"/condition"@RequestMapping()
value"condition""#name.length() <= 4"@Cacheable(=,condition=)
public String condition(String name) {
out"没有走缓存!"    System..println();
return"hello "   +name;
}

启动后在浏览器中输入网址 http://localhost:8080/condition?name=neo,第一次输出栏输出:没有走缓存!再次执行无输出,代表已经走缓存。在浏览器中输入网址 http://localhost:8080/condition?name=ityouknow,浏览器执行屡次仍然一直输出:没有走缓存!说明条件 condition 生效。

结合数据库的使用来作测试:

"/getUsers"@RequestMapping()
value"usersCache""#nickname""#nickname.length() >= 6"@Cacheable(=,key=,condition=)
public List<User> getUsers(String nickname) {
    List<User> users=userRepository.findByNickname(nickname);
out"执行了数据库操做"    System..println();
return   users;
}

启动后在浏览器中输入网址 http://localhost:8080/getUsers?nickname=neo

输出栏输出:

selectasasasasasasfromuserwhereHibernate:user0_.idid1_0_, user0_.emailemail2_0_, user0_.nicknamenickname3_0_, user0_.pass_wordpass_wor4_0_, user0_.reg_timereg_time5_0_, user0_.user_nameuser_nam6_0_user0_user0_.nickname=?
执行了数据库操做

屡次执行,仍然输出上面的结果,说明每次请求都执行了数据库操做,再输入 http://localhost:8080/getUsers?nickname=ityoukonw 进行测试。只有第一次返回了上面的内容,再次执行输出栏没有变化,说明后面的请求都已经从缓存中拿取了数据。

最后总结一下:当执行到一个被 @Cacheable 注解的方法时,Spring 首先检查 condition 条件是否知足,若是不知足,执行方法,返回;若是知足,在缓存空间中查找使用 key 存储的对象,若是找到,将找到的结果返回,若是没有找到执行方法,将方法的返回值以 key-value 对象的方式存入缓存中,而后方法返回。

须要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。

1.1.2  @CachePut

项目运行中会对数据库的信息进行更新,若是仍然使用 @Cacheable 就会致使数据库的信息和缓存的信息不一致。在以往的项目中,咱们通常更新完数据库后,再手动删除掉 Redis 中对应的缓存,以保证数据的一致性。Spring 提供了另外的一种解决方案,可让咱们以优雅的方式去更新缓存。

与 @Cacheable 不一样的是使用 @CachePut 标注的方法在执行前,不会去检查缓存中是否存在以前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

以上面的方法为例,咱们再来作一个测试:

"/getPutUsers"@RequestMapping()
value"usersCache""#nickname"@CachePut(=,key=)
public List<User> getPutUsers(String nickname) {
    List<User> users=userRepository.findByNickname(nickname);
out"执行了数据库操做"    System..println();
return   users;
}

咱们新增一个 getPutUsers 方法,value、key 设置和 getUsers 方法保持一致,使用 @CachePut。同时手动在数据库插入一条 nikename 为 ityouknow 的用户数据。

INSERTINTO`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 也能够标注在类上和方法上。

1.1.3  @CacheEvict

@CacheEvict 是用来标注在须要清除缓存元素的方法或类上的,当标记在一个类上时表示其中全部的方法的执行都会触发缓存的清除操做。@CacheEvict 能够指定的属性有 value、key、condition、allEntries 和 beforeInvocation,其中 value、key 和 condition 的语义与 @Cacheable 对应的属性相似。

即 value 表示清除操做是发生在哪些 Cache 上的(对应 Cache 的名称);key 表示须要清除的是哪一个 key,如未指定则会使用默认策略生成的 key;condition 表示清除操做发生的条件。下面来介绍一下新出现的两个属性 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 为名的缓存所有清除。咱们来看个例子:

"/allEntries"@RequestMapping()
value"usersCache"true@CacheEvict(=, allEntries=)
public List<User> allEntries(String nickname) {
    List<User> users=userRepository.findByNickname(nickname);
out"执行了数据库操做"    System..println();
return   users;
}

手动修改用户表的相关信息,好比注册时间。在浏览器中输入网址 http://localhost:8080/getUsers?nickname=ityouknow 发现缓存中的数据并无更新,再次访问地址 http://localhost:8080/getUsers?nickname=ityouknow 会发现数据已经更新,而且输出栏输出“执行了数据库操做”,这代表已经将名为 usersCache 的缓存记录清空了。

beforeInvocation 属性

清除操做默认是在对应方法成功执行以后触发的,即方法若是由于抛出异常而未能成功返回时也不会触发清除操做。使用 beforeInvocation 能够改变触发清除操做的时间,当咱们指定该属性值为 true 时,Spring 会在调用该方法以前清除缓存中的指定元素。

@RequestMapping"/beforeInvocation"()
@CacheEvict"usersCache"truetrue(value=, allEntries=, beforeInvocation=)
public void beforeInvocation() {
thrownew"test beforeInvocation"   RuntimeException();
}

咱们来作一个测试,在方法中添加一个异常,访问网址 http://localhost:8080/beforeInvocation查看 usersCache 的缓存是否被更新。

按照上面的实验步骤,手动修改用户表的相关信息,访问网址 http://localhost:8080/getUsers?nickname=ityouknow 发现缓存中的数据并无更新;再访问网址 http://localhost:8080/beforeInvocation 会报错误,先不用管这里,再次访问地址 http://localhost:8080/getUsers?nickname=ityouknow 会发现数据已经更新,而且输出栏输出“执行了数据库操做”。这代表虽然在测试的过程当中方法抛出了异常,但缓存中名为 usersCache 的记录都已被清空。

1.1.4  总结一下其做用和配置方法

@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 三个注解很是灵活,知足了咱们对数据缓存的绝大多数使用场景,而且使用起来很是的简单而又强大,在实际工做中咱们能够灵活搭配使用。

1.2    总结

Spring 提供了基于注释驱动的 Spring Cache,它是一个对缓存使用的抽象,将咱们经常使用的缓存策略都进行了高度抽象,让咱们在项目中使用时只须要添加几个注解,便可完成大多数缓存策略的实现。Spring Boot Starter Cache 是 Spring Boot 提供给咱们在 Spring Boot 中使用 Spring Cache 的 Starter 包,集成后方便在 Spring Boot 体系中使用缓存。

点击这里下载源码

相关文章
相关标签/搜索