目录:
- 一、Spring Boot 默认 "缓存" 管理 :
- 二、Spring Boot "缓存注解" 介绍 :
- 2.1 @EnableCaching 注解 ( 用在 "主程序启动类" 上,用于开启SpringBoot的 "缓存管理支持")
- 2.2 @Cacheable( ) 注解 ( 作用于"类" 或 "方法",通常用在 "数据查询" 功能的 "方法" 上 , 将 "查询结果" 存进 "缓存空间" 中 )
- 2.3 @CachePut 注解 ( 作用于"类" 或 "方法",通常用在 "数据更新" 功能的 "方法" 上 ,在 "更新数据库" 后 "更新缓存" )
- 2.4 @CacheEvict 注解 ( 作用于"类" 或 "方法",通常用在 "数据删除" 方法上 ,在”删除数据库中 数据“ 后,进行删除 “缓存数据” )
- 2.5 @Caching注解 ( 作用于"类" 或 "方法" )
- 2.6 @CacheConfig注解 ( 作用于"类" , 为该类下 的 "缓存注解" 设置 “公共属性” )
作者简介 :一只大皮卡丘,计算机专业学生,正在努力学习、努力敲代码中! 让我们一起继续努力学习!
该文章参考学习教材为:
《Spring Boot企业级开发教程》 黑马程序员 / 编著
文章以课本知识点 + 代码为主线,结合自己看书学习过程中的理解和感悟 ,最终成就了该文章文章用于本人学习使用 , 同时希望能帮助大家。
欢迎大家点赞👍 收藏⭐ 关注💖哦!!!(侵权可联系我,进行删除,如果雷同,纯属巧合)
- 缓存是分布式系统中的重要组件,主要 解决数据厍数据的 高并发访问问题。在实际开发中,尤其是用户访问量较大的网站,为了提高服务器访问性能、减少数据库的压力、提高用户体验,使用 缓存显得尤为重要。Spring Boot对缓存提供了良好的支持。
一、Spring Boot 默认 “缓存” 管理 :
- Spring框架支持透明地向应用程序添加缓存并对缓存进行管理,其管理缓存的 核心是将缓存 应用于操作数据的方法中,从而 减少操作 数据的次数,同时不会对程序本身造成任何干扰。
- Spring Boot继承了Spring框架的缓存管理功能,通过使用 @EnableCaching注解开启基于注解的 缓存支持,Spring Boot可以启动缓存管理的自动化配置。
1.1 基础环境搭建
- 使用缓存的主要目的是减少数据库数据的访问压力、提高用户体验,下面代码例子将结合数据库的访问操作对 Spring Boot 的缓存管理讲行演示说明。
① 准备数据
先创建了一个 数据库springbootdata,然后创建了两个表 t_article和t_comment ,并向表中插入数据。
其中评论表t_comment 的a_id 与文章表t_article 的主键id 相关联 ( t_article的主键作为t_comment表的 “外键”)。
② 创建项目
创建项目,引入相关依赖 :
③ 编写 “数据库表” 对应的 “实体类”
编写t_comment表对应的 实体类Comment,并用 JPA相关注解配置映射关系 :
Comment.java :
package com.myh.chapter_14.domain; import jakarta.persistence.*; //指定该实现类映射的数据库表 @Entity(name = "t_commet") //设置ORM实体类, 并指定对应的表明 public class Comment { //表示数据库表中主键对应的属性 @Id @GeneratedValue(strategy= GenerationType.IDENTITY) //设置主键的生成策略 (主键自增) private Integer id; @Column(name = "content") //指定映射的表字段名 private String content; @Column(name = "author") private String author; @Column(name = "a_id") private Integer aId; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public Integer getaId() { return aId; } public void setaId(Integer aId) { this.aId = aId; } @Override public String toString() { return "Comment{" + "id=" + id + ", content='" + content + '\'' + ", author='" + author + '\'' + ", aId=" + aId + '}'; } }
④ 编写 “操作数据库” 的 Repository接口文件
在项目中创建 repository包,在该包下创建一个用于操作数据库的 自定义的Repository接口 ,该接口 继承自 JpaRepository
( Repository接口 中为 操作数据库的方法 )
CommentRepository.java :
package com.myh.chapter_14.Repository; import com.myh.chapter_14.domain.Comment; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; //Repository接口中为操作数据库的方法 /* 继承了JpaRepository接口,其中有操作数据库的curd方法,也用方法关键字的形式来操作数据库,或者使用@Query注解的方式来操作数据库 */ public interface CommentRepository extends JpaRepository<Comment,Integer> { //根据评论id来修改评论作者author //通过updateComment()方法对应的@Query注解来操作数据库 @Query("update t_commet c set c.author = ?1 where c.id = ?2") //通过该标签来操作数据库 public int updateComment(String author, Integer id); }
⑤ 编写 “业务操作列” Service文件
在项目中创建 service包,在该包下创建一个用于操作Comment相关业务操作的 Service实体类 :
CommentController.java
package com.myh.chapter_14.controller; import com.myh.chapter_14.domain.Comment; import com.myh.chapter_14.service.CommentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; //@ResponseBody注解 : 将方法的返回值转换为"指定类型",存入响应体中,然后响应给“前端” @RestController // 该注解等于 @ResponseBody 注解 + @Controller注解 public class CommentController { @Autowired private CommentService commentService; /** * findById()方法 */ @GetMapping("/get/{id}") //路径变量,用@PathVariable()注解来接受路径变量 public Comment findById(@PathVariable("id") int comment_id) { Comment comment = commentService.findById(comment_id); return comment; } /** * updateComment()方法 */ @GetMapping("/update/{id}/{author}") //路径变量,用@PathVariable()注解来接受路径变量 public Comment updateComment(@PathVariable("id") int comment_id,@PathVariable("author") String author) { Comment comment = commentService.findById(comment_id); comment.setAuthor("张三"); Comment updateComment = commentService.updateComment(comment); return updateComment; } /** * deleteComment()方法 */ @GetMapping("/delete/{id}}") //路径变量,用@PathVariable()注解来接受路径变量 public void deleteComment(@PathVariable("id") int comment_id) { commentService.deleteComment(comment_id); } }
⑥ 编写 “application.properties配置文件”
application.properties :
#配置数据库信息 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT&nullCatalogMeansCurrent=true spring.datasource.username=root spring.datasource.password=root #显示使用JPA进行"数据库查询"的SQL语句,用于展示操作的Sql语句 #这个属性决定了是否应该在控制台打印出SQL查询语句 spring.jpa.show-sql=true
⑦ 项目测试 ( 实际开发中的"问题突显",用 “缓存技术” 能解决这个问题 )
启动项目,项目启动成功后,在浏览器上访问 http://localhost:8080/get/1 查询 id为1的用户评论信息,此时 假设我在浏览器一直刷新访问这个 网址,就会有以下 这种情况:
浏览器每刷新访问一次,服务器端的 控制台就会输出一条sql语句,同时也会再执行一次sql操作,但是页面显示的还是那一条数据 。( 存在一个实际开发中的问题,这个问题会消耗数据库的性能,消耗服务器资源,同时数据库性能的下降也会影响 用户的体验 ,这是一个要被解决的问题 )
之所以出现上面两图的情况,这是因为 没有在 Spring Boot项目中 开启缓存管理。在没有缓存管理的情况下,虽然数据表中的数据没有发生变化,但是 每执行 一次查询操作(本质是执行同样的SQL 语句),都会访问一次数据库并执行一次SQL 语句。
随着时间的积累,系统的用户不断增加,数据规模越来越大,数据库的操作会直接影响用户的使用体验,此时使用缓存往往是解决这一问题非常好的一种手段。
1.2 Spring Boot “默认缓存体验”
- 在 前面搭建 的 Web应用 基础上,开启Spring Boot默认支持 的 缓存, 体验Spring Boot缓存 的 使用效果。
(1) 使用 “@EnableCaching” 注解 开始 “缓存管理”
使用 @EnableCaching注解开启 基于 注解的缓存支持 ,在项目启动类中加入该注解即可 :
@SpringBootApplication @EnableCaching //开启SpringBoot基于注解的"缓存管理"支持 public class Chapter14Application { public static void main(String[] args) { SpringApplication.run(Chapter14Application.class, args); } }
(2) 使用 “@Cacheable( )” 注解对 “数据操作方法” 进行 “缓存管理” ( 将该注解放在service类的“操作方法”上,让其对"查询结果" 进行 “缓存” )
使用 “@Cacheable ( )” 注解对 “数据操作方法” 进行 “缓存管理”。 将 @Cacheable( )注解标注在 Service类的 查询方法 上,对查询结果进行缓存 :
CommentService.java :
package com.myh.chapter_14.service; import com.myh.chapter_14.Repository.CommentRepository; import com.myh.chapter_14.domain.Comment; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Controller; import org.springframework.stereotype.Service; import java.util.Optional; /** * 使用 @Cacheable()注解开启“缓存管理” : 将查询到的结果存储到“缓存空间”中,下次访问该方法时,不会再去查数据库,而是从“缓存空间”中拿数据 */ @Service //将该类加入到ioc容器中 public class CommentService { //业务操作类 : 关于操作Comment类相关的业务的CommentService业务类 ( 该类执行业务操作代码 ) @Autowired private CommentRepository commentRepository; /** * 调用CrudRepository接口中的findById()方法来操作数据库 */ /* ①@Cacheable()注解的作用 : 添加该注解后,Spring在执行该方法之前会检查缓存中是否有“该查询”的结果,如果有则从其中取出结果,如果没有才去数据库查询 ②@Cacheable("comment") 中的comment为该"缓存空间"的名称,用区分不同的缓存 key = "comment_id" 设置该缓存数据的key(“缓存数据”的唯一标识),key属性的值默认为: 方法中参数的值 key : 表示"缓存数据"的唯一标识 */ // @Cacheable(cacheNames = "comment",key = "comment_id") //开启“缓存管理”,缓存空间名称为: comment public Comment findById(int comment_id) { //comment_id的值默认为 该“缓存”的唯一标识( "缓存数据"对应的“key”) //调用CrudRepository接口中的findById()方法来操作数据库,有一个返回值类型为Optional<T>类型的对象 Optional<Comment> optional = commentRepository.findById(comment_id); if (optional.isPresent()) { //用于检查Optional对象"是否包含"一个"值" return optional.get(); //返回这个Optional对象包含的"值" } return null; } }
上述代码中,在CommentService类中的 findByld( int comment_id )方法上添加了 @Cacheable( )注解,该注解的作用是 将 查询结果 : Comment 存放
在 Spring Boot默认缓存 中 名称为 : comment 的 名称空间 中。对应缓存的唯一标识 ( 即缓存数据对应的主键 key ) 默认为方法参数comment_id的值。
(3) Spring Boot默认缓存测试
启动项目,通过浏览器继续访问 “http:/localhost:8080/get/1” 查询id为1的用户评论信息。此时不论浏览器刷新多少次,访问同一个用户评论信息,页面的查询结果都会显示同一条数据,但**重点**是 后端不用进行多次数据库操作了,此时后端只进行了一次数据库操作。
二、Spring Boot “缓存注解” 介绍 :
- 在上面的内容我们通过使用 @EnableCaching、@Cacheable 注解实现了 Spring Boot默认的基于注解 的 缓存管理,除此之外,还有更多的 缓存注解以及注解属性可以配置优化缓存管理。
2.1 @EnableCaching 注解 ( 用在 “主程序启动类” 上,用于开启SpringBoot的 “缓存管理支持”)
@EnableCaching 注解 是由 Spring框架提供的,Spring Boot框架对该注解进行了 继承,该注解需要 配置在类上(在Spring Boot中 ,通常 配置在项目启动类上),用于 开启基于注解的 缓存支持。
@EnableCaching 注解 代码例子如 :
Chapter14Application.java ( 主程序启动类 ) :
@SpringBootApplication @EnableCaching //开启SpringBoot的"缓存管理支持" public class Chapter14Application { public static void main(String[] args) { SpringApplication.run(Chapter14Application.class, args); } }
2.2 @Cacheable( ) 注解 ( 作用于"类" 或 “方法”,通常用在 “数据查询” 功能的 “方法” 上 , 将 “查询结果” 存进 “缓存空间” 中 )
@Cacheable 注解也是由 Spring 框架提供的,可以作用于类或方法(通常用 在 数据查询查法 上),用于 对方法的查询结果 进行缓存存储 ( 将查询结果 存储在 “缓存空间” 中 )。
( @Cacheable 注解注解一般用在 select 功能 的方法上 ,当调用该方法时,先根据key去“缓存空间”中查询是否有“缓存数据”,如果有,自然就用该“缓存数据” , 如果没有,则去数据库查询数据,然后将该数据 存储到 “缓存空间”中,作为“ 缓存数据”)
@Cacheable( )注解的执行顺序是,先进行"缓存查询" ( 即在缓存空间查询是否有符合要求的数据 ) ,如果 为空则 进行 “方法查询” ;如果不为空,则直接使用缓存数据 ( 此时则不再进行"方法查询" )
@Cacheable( )注解有多个属性,用于对“缓存存储”进行相关配置,具体属性 及 说明 如下表所示 :
属性名 说明 value / cacheNames 指定缓存空间的名称,必配属性。这两个属性 二选一使用。
ps :
如果 @Cacheable( ) 注解 只有 value 或 cacheNames( ) 这 一个属性时,则该属性的属性名可省略。key 指定 缓存数据的 key ( 该 “ 缓存”的 唯一标识),默认使用 方法参数值,可以使用SpEL表达式。 keyGenerator 指定 缓存数据 的 key 的 生成器,与key属性二选一使用。 cacheManager 指定 缓存管理器。 cacheResolver 指定 缓存解析器,与 cacheManager 属性二选一 使用。 condition 指定在符合某条件下,“进行” 数据缓存。 unless 指定在符合某条件下,“不进行” 数据缓存。 sync 指定 是否使用 “异步缓存”。默认 false。 @Cacheable( ) 注解 代码例子如 :
CommentService.java :
/** * 使用 @Cacheable()注解开启“缓存管理” : 将查询到的结果存储到“缓存空间”中,下次访问该方法时,不会再去查数据库,而是从“缓存空间”中拿数据 */ @Service //将该类加入到ioc容器中 public class CommentService { //业务操作类 : 关于操作Comment类相关的业务的CommentService业务类 ( 该类执行业务操作代码 ) @Autowired private CommentRepository commentRepository; /* @Cacheable()注解的作用 : 添加该注解后,Spring在执行该方法之前会检查缓存中是否有“该查询”的结果,如果有则从其中取出结果,如果没有才去数据库查询 @Cacheable("comment") 中的comment为该"缓存空间"的名称,用区分不同的缓存 */ @Cacheable(cacheNames = "comment") //开启“缓存管理”,该"缓存空间"名称为: comment /* 如果没有显式地指定该"缓存空间"的key,那么该方法的"参数的值"则为该"key的值", 所以此处有 comment_id这个参数的值默认为 该“缓存”的唯一标识( "缓存数据"对应的“key”) ,value则是该"缓存值" */ public Comment findById(int comment_id) { //调用CrudRepository接口中的findById()方法来操作数据库,有一个返回值类型为Optional<T>类型的对象 Optional<Comment> optional = commentRepository.findById(comment_id); if (optional.isPresent()) { //用于检查Optional对象"是否包含"一个"值" return optional.get(); //返回这个Optional对象包含的"值" } return null; } }
value / cacheNames 属性 ( 指定存储"缓存数据"的 “空间的名称” )
value属性 和 cacheNames 属性 作用相同,用于 指定缓存的 名称空间 ( 指定“缓存空间”的 名称 ),可以同时指定多个名称空间(例如 @Cacheable ( cacheNames = {“comment1”,“comment2”} ) )。
如果 @Cacheable( ) 注解 只有 value 或 cacheNames( ) 这 一个属性时,则该属性的属性名可省略,例如 @Cacheable(“comment”)指定了缓存的名称空间为 comment。
@Cacheable( ) 注解的 value / cacheNames 属性 的 代码例子 如 :
CommentService.java :
/** * 使用 @Cacheable()注解开启“缓存管理” : 将查询到的结果存储到“缓存空间”中,下次访问该方法时,不会再去查数据库,而是从“缓存空间”中拿数据 */ @Service //将该类加入到ioc容器中 public class CommentService { @Autowired private CommentRepository commentRepository; /* @Cacheable()注解的作用 : 添加该注解后,Spring在执行该方法之前会检查缓存中是否有“该查询”的结果,如果有则从其中取出结果,如果没有才去数据库查询 @Cacheable("comment") 中的comment为该"缓存空间"的名称,用区分不同的缓存 */ @Cacheable(cacheNames = "comment") //开启“缓存管理”,该"缓存空间"名称为: comment public Comment findById(int comment_id) { //调用CrudRepository接口中的findById()方法来操作数据库,有一个返回值类型为Optional<T>类型的对象 Optional<Comment> optional = commentRepository.findById(comment_id); if (optional.isPresent()) { //用于检查Optional对象"是否包含"一个"值" return optional.get(); //返回这个Optional对象包含的"值" } return null; } //该方法的"查询结果"会被缓存在 comment 和 commentCache 这两个"缓存空间"中 @Cacheable(cacheNames = {"comment", "commentCache"}) public Comment findById2(int comment_id) { Optional<Comment> optional = commentRepository.findById(comment_id); if (optional.isPresent()) { return optional.get(); } return null; } }
key 属性 ( 指定“缓存数据” 对应的 “key / 唯一标识” , 通过这个key就能找到该“缓存数据” ,key属于 和 keyGenerator属性 “二选一” 使用 )
key属性 的作用 是指定缓存数据 对应的 唯一标识,① 默认使用注解标记的 方法 中的 “参数 的 值” ( 默认使用方法中的参数的值作为key的值 ),② 也可以使用 SpEL表达式。
缓存数据的本质是Map类型数据,key 用于 指定唯一的 标识,value用于指定 缓存数据。
如果缓存数据时,没有指定key属性,Spring boot默认提供的配置类 SimpleKeyGenerator会通过 generateKey( Object…params)方法参数生成key值。默认情况下,如果 generateKey( )方法有一个参数,参数值就是key属性的值;如果generateKey( )方法没有参数,那么key属性是一个空参的 SimpleKey[]对象,如果有多个参数,那么 key属性是一个带参的 SimpleKey[params1],[param2,…]]对象。
除了 使用默认key属性值外,还可以手动指定key属性值,或者是使用Spring框架提供的 SpEL表达式。关于**缓存中支持的
SpEL表达式及说明如下表所示** :
名称 位置 描述 methodName root对象 当前被调用的 方法名 #root.methodName method root对象 当前被调用的方法 #root.method.name target root对象 当前被调用的 目标对象实例 #root.target targetClass root对象 当前被调用的目标对象的类 #root.targetClass args root 对象 当前 被调用的方法的参数列表 #root.args[0] caches root对象 当前 被调用的方法的缓存列表 #root.caches[0].name Argument 执行上下文 当前 被调用的方法参数,可以用#参数名或者#a0、#p0的形式表示
( 0代表参数索引,从0开始 )#comment_id、#a0、#p0 result 执行上下文 当前方法执行后的返回结果 #result @Cacheable( ) 注解的 key 属性 的 代码例子 如 :
CommentService.java :
/** * 使用 @Cacheable()注解开启“缓存管理” : 将查询到的结果存储到“缓存空间”中,下次访问该方法时,不会再去查数据库,而是从“缓存空间”中拿数据 */ @Service //将该类加入到ioc容器中 public class CommentService2 { //业务操作类 @Autowired private CommentRepository commentRepository; /* 如果没有显式地指定该"缓存空间"的key,那么该方法的"参数的值"则为该key的值, 所以此处有 comment_id这个参数的值默认为 该“缓存”的唯一标识( "缓存数据"对应的“key”) 显式地指定该"缓存空间"的key, key为方法中参数的"值",此时value就是该"缓存空间"中的"缓存值" */ /** * 1.使用"方法参数" 作为 key * * #comment_id 是一个 "SpEL表达式" , 配置该key,意味着每次调用该方法时,都会以key="comment_id的值",来在"缓存空间"中查找 "缓存值" */ @Cacheable(cacheNames = "comment",key = "#comment_id") //显式地指定该"缓存空间"的key为comment_id这个参数的"值" public Comment findById(int comment_id) { //调用CrudRepository接口中的findById()方法来操作数据库,有一个返回值类型为Optional<T>类型的对象 Optional<Comment> optional = commentRepository.findById(comment_id); if (optional.isPresent()) { //用于检查Optional对象"是否包含"一个"值" return optional.get(); //返回这个Optional对象包含的"值" } return null; } /** * 2.使用 多个 "方法参数" 作为 key * * "#comment_id + '-' +#userName" : * 是一个 "SpEL表达式" , 配置该key,意味着每次调用该方法时,都会以key="#comment_id + '-' +#userName"的值,来在"缓存空间"中查找 "缓存值" */ @Cacheable(cacheNames = "comment",key = "#comment_id + '-' +#userName") public Comment findById2(int comment_id,String userName) { //调用CrudRepository接口中的findById()方法来操作数据库,有一个返回值类型为Optional<T>类型的对象 Optional<Comment> optional = commentRepository.findById(comment_id); if (optional.isPresent()) { //用于检查Optional对象"是否包含"一个"值" return optional.get(); //返回这个Optional对象包含的"值" } return null; } /** * 3.使用 "方法参数" 集合 "对象的值" 作为 key * * "#comment_id + '-' +#user.id" : * 是一个 "SpEL表达式" , 配置该key,意味着每次调用该方法时,都会以key="#comment_id + '-' +#user.id"的值,来在"缓存空间"中查找 "缓存值" */ @Cacheable(cacheNames = "comment",key = "#comment_id + '-' +#user.id") public Comment findById3(int comment_id, User user) { //调用CrudRepository接口中的findById()方法来操作数据库,有一个返回值类型为Optional<T>类型的对象 Optional<Comment> optional = commentRepository.findById(comment_id); if (optional.isPresent()) { //用于检查Optional对象"是否包含"一个"值" return optional.get(); //返回这个Optional对象包含的"值" } return null; } }
CommentController.java :
//@ResponseBody注解 : 将方法的返回值转换为"指定类型",存入响应体中,然后响应给“前端” @RestController // 该注解等于 @ResponseBody 注解 + @Controller注解 public class CommentController { @Autowired private CommentService commentService; @GetMapping("/get/{id}") //路径变量,用@PathVariable()注解来接受路径变量 public Comment findById(@PathVariable("id") int comment_id) { Comment comment = commentService.findById(comment_id); return comment; } @GetMapping("/get/{id}/{userName}") public Comment findById2(@PathVariable("id") int comment_id,@PathVariable("userName") String userName) { Comment comment = commentService.findById2(comment_id,userName); return comment; } @GetMapping("/get3/{id}") public Comment findById3(@PathVariable("id") int comment_id, User user) { Comment comment = commentService.findById3(comment_id,user); return comment; } }
keyGenerator属性 ( keyGenerator属性 : 指定生成key的 key值生成器",该属性和 key属性 “二选一” 使用 )
keyGenerator属性与kev属性 本质作用 相同,都是用于指定缓存数据的key,只不过 keyGenerator 属性指定的不是具休的 key值,而是 key值的生成器规则,由其中 指定的生成器 生成 具体的key 。使用时, kevGenerator属性与key属性要 二者选一。关于自定义key值生成器的定义,可以参考Sprina Boot默认配置类SimoleKavGenerator的定义方式。
@Cacheable( ) 注解 的 keyGenerator属性 的代码例子如 :
CommentService.java :
/** * 使用 @Cacheable()注解开启“缓存管理” : 将查询到的结果存储到“缓存空间”中,下次访问该方法时,不会再去查数据库,而是从“缓存空间”中拿数据 */ @Service //将该类加入到ioc容器中 public class CommentService { //业务操作类 @Autowired private CommentKeyGenerator commentKeyGenerator; /** * 通过实现KeyGenerator接口,在CommentKeyGenerator中"自定义"生成key的值 * ( 现KeyGenerator接口是属于 keyGenerator属性的内容,该属于与 key属性 "二选一" 使用) * (KeyGenerator属性 和 key属性 "二选一" 使用 ) */ @Cacheable(cacheNames = "comment",keyGenerator ="commentKeyGenerator" ) //commentKeyGenerator 为 自定义的 "keyGenerator" public Comment findById4(int comment_id,String userName) { //调用CrudRepository接口中的findById()方法来操作数据库,有一个返回值类型为Optional<T>类型的对象 Optional<Comment> optional = commentRepository.findById(comment_id); if (optional.isPresent()) { //用于检查Optional对象"是否包含"一个"值" return optional.get(); //返回这个Optional对象包含的"值" } return null; } }
CommentKeyGenerator.java :
/** * 在该类中配置key的生成规则 */ @Component //加入到IOC容器中 public class CommentKeyGenerator implements KeyGenerator { @Override public Object generate(Object target, Method method, Object... params) { int commentId = (int) params[0]; String userName = (String) params[1]; return commentId + "_" + userName; } }
cacheManager / cacheResolver属性
cacheManager ( 缓存管理器 )和 cacheResolver ( 缓存解析器 )属性分别用于指定 缓存管理器和缓存解析器,这两个属性也是
二选一使用,默认情况不需要配置,如果存在多个cacheManager : 缓存管理器(如 Redis、Ehcache 等 ),可以 使用这两个属性分别指定。
condition 属性 ( 指定条件为true时,才对“查询结果” 进行 缓存 )
condition属性用于对数据进行 有条件的选择性存储,只有当 指定条件 为 true时才会对查询结果进行缓存,可以使用 SpEL表达式指定属性值。
例如 : @Cacheable(cacheNames = “comment ,condition =”#comment_id>10") 表示方法参数comment_id的 值大于10 才会对 结果数据进行缓存。
@Cacheable( ) 注解 的 condition 属性 的代码例子如 :
CommentService.java :
/** * 使用 @Cacheable()注解开启“缓存管理” : 将查询到的结果存储到“缓存空间”中,下次访问该方法时,不会再去查数据库,而是从“缓存空间”中拿数据 */ @Service //将该类加入到ioc容器中 public class CommentService { //业务操作类 @Autowired private CommentRepository commentRepository; /** * @Cacheable()注解中的 condition属性 的使用 : 当指定条件为true时,才会对"查询结果"进行"缓存" * condition = "#comment_id > 3" : 表示当comment_id的值大于3时,才会对相应的数据进行“缓存管理”,否则不会对数据进行"缓存管理" */ @Cacheable(cacheNames = "comment",condition = "#comment_id > 3") public Comment findById5(int comment_id) { //调用CrudRepository接口中的findById()方法来操作数据库,有一个返回值类型为Optional<T>类型的对象 Optional<Comment> optional = commentRepository.findById(comment_id); if (optional.isPresent()) { //用于检查Optional对象"是否包含"一个"值" return optional.get(); //返回这个Optional对象包含的"值" } return null; } }
CommentController.java :
//@ResponseBody注解 : 将方法的返回值转换为"指定类型",存入响应体中,然后响应给“前端” @RestController // 该注解等于 @ResponseBody 注解 + @Controller注解 public class CommentController { @Autowired private CommentService commentService; @GetMapping("/get5/{id}") //路径变量,用@PathVariable()注解来接受路径变量 public Comment findById35(@PathVariable("id") int comment_id) { Comment comment = commentService.findById5(comment_id); return comment; } }
unless属性 ( 指定条件为false时,才对“查询结果” 进行 缓存 )
unless属性的作用与 condition属性相反,当指定的条件为 true 时,方法的返回值不会被缓存 ( 即当 指定条件为 true时,此时 不会进行“缓存” ,指定条件为false,才会进行数据的缓存 )。unless属性可以使用 SpEL表达式指定。
例如 :@Cacheable(cacheNames = "comment ,unless= “#result==nul”) 表示只有查询结果不为空才会对结果数据进行缓存存储。
@Cacheable( ) 注解 的 unless属性 的代码例子如 :
CommentService.java :
/** * 使用 @Cacheable()注解开启“缓存管理” : 将查询到的结果存储到“缓存空间”中,下次访问该方法时,不会再去查数据库,而是从“缓存空间”中拿数据 */ @Service //将该类加入到ioc容器中 public class CommentService2 { //业务操作类 @Autowired private CommentRepository commentRepository; /** * @Cacheable()注解中的 unless 的使用 : 当指定条件为false时,才会对"查询结果"进行"缓存" * cunless = "#comment_id == -1") : 表示当comment_id的值不等于 -1时,才会对相应的数据进行“缓存管理”,否则不会对数据进行"缓存管理" */ @Cacheable(cacheNames = "comment",unless = "#comment_id == -1") //unless : 指定条件为 false才对“查询结果”进行缓存 public Comment findById6(int comment_id) { System.out.println("get6"); //调用CrudRepository接口中的findById()方法来操作数据库,有一个返回值类型为Optional<T>类型的对象 Optional<Comment> optional = commentRepository.findById(comment_id); if (optional.isPresent()) { //用于检查Optional对象"是否包含"一个"值" return optional.get(); //返回这个Optional对象包含的"值" } return null; } }
sync属性
sync属性表示数据缓存过程中是否使用异步模式,默认值为 false。
2.3 @CachePut 注解 ( 作用于"类" 或 “方法”,通常用在 “数据更新” 功能的 “方法” 上 ,在 “更新数据库” 后 “更新缓存” )
① @CachePut注解是由Spring框型提供的,可以作用于 类 或 方法 ( 通常用在"数据更新" 方法 上),该 注解的作用 是 : "更新"缓存数据 。
( 该注解 一般用在 update功能 的 方法 上 ,含义为 : 更新)② CachePut 注解 的 执行顺序 是 先进行"方法调用" ,然后 将"方法结果" 更新到缓存 中。
③ @CachePut注解也提供了多个属性,这些属性 与 @Cacheable注解 的 属性完全相同。
@CachePut 注解 代码例子如 :
CommentController.java :
@RestController public class CommentController { @Autowired private CommentService commentService; /** * 根据id查询数据 ( 将在service层中添加"缓存管理" ) */ @GetMapping("/findCommentById/{id}") public Comment findCommentById(@PathVariable("id") Integer id) { //使用@PathVariable路径变量注解来获取“路径下的变量” Comment comment = commentService.findCommentById(id); return comment; } /** * 更新数据 * 在service层中将 updateComment()方法的"返回值" 更新到"缓存空间"中, 更新"缓存数据" , 这样下次调用 findCommentById()方法的时候就能 * 获得最新的"缓存空间"的"缓存数据" */ @GetMapping("/updateComment/{id}/{author}") public Comment updateComment(@PathVariable("id") Integer id,@PathVariable("author") String author) { Comment comment = commentService.updateComment(id,author); return comment; } }
CommentService.java :
@Service //将该类加入到ioc容器中 public class CommentService { @Autowired private CommentRepository commentRepository; /** * 使用@CachePut注解来当update数据库后,用该方法的返回值来更新“缓存空间”中的"缓存数据" */ @Cacheable(cacheNames = "comment",key = "#id",condition = "#id > 0") //id参数的值大于0,才对数据进行"缓存管理" public Comment findCommentById(Integer id) { Optional<Comment> optional = commentRepository.findById(id); //判断Optional是否为空 if (optional.isPresent()) { //用于检查Optional对象"是否包含"一个"值" return optional.get(); //返回这个Optional对象包含的"值" } return null; } /** * 使用@CachePut()注解来 更新"缓存数据" , 将 updateComment()方法的"返回值" 更新存储到 "缓存空间"中 * * 因为 @CachePut()注解 和 @Cacheable()注解的 “缓存空间名” 和 "key的值" 相同,所以,此处在update数据后,也会及时将“更新数据”存储进“缓存空间”中 * 所以此时在调用 findCommentById()方法时,会从"缓存空间"中获得“更新后的缓存数据” */ @CachePut(cacheNames = "comment",key = "#id") public Comment updateComment(Integer id, String author) { Comment comment = commentRepository.findById(id).get(); comment.setAuthor(author); //更新数据 comment = commentRepository.save(comment); return comment; } }
CommentRepository.java :
package com.myh.chapter_14.Repository; import com.myh.chapter_14.domain.Comment; import org.springframework.data.jpa.repository.JpaRepository; //Repository接口中为操作数据库的方法 /* 继承了JpaRepository接口,其中有操作数据库的curd方法,也用方法关键字的形式来操作数据库,或者使用@Query注解的方式来操作数据库 */ //继承JpaRepository接口,从其中获得操作数据库的方法 public interface CommentRepository extends JpaRepository<Comment,Integer> { }
2.4 @CacheEvict 注解 ( 作用于"类" 或 “方法”,通常用在 “数据删除” 方法上 ,在”删除数据库中 数据“ 后,进行删除 “缓存数据” )
① @CacheEvict 注解是由Spring框架提供的,可以作用于 类或方法(通常用在 “数据删除” 方法上),该 注解的作用是"删除" 缓存数据。
② @CacheEvict 注解的默认执行顺序是 : 先进行方法调用,然后清除缓存。( 默认是先执行方法进行数据库删除,然后再清除 “缓存”)
③ @CacheEvict注解提供了多个属性,这些属性与 @Cacheable注解 的 属性基本相同。除此之外,@CacheEvic注解 额外提供了两个特殊属性 : allEntries 和 beforelnvocation ,其说明如下 :
(1) allEntries属性 :
allEntries属性表示是否清除指定缓存空间中的所有缓存数据,默认值为false ( 即 默认只删除指定key对应的 缓存数据 )。
例如 :
@CacheEvict (cacheNames = “comment” , allEntries = true ) 表示 : 方法执行后会删除缓存空间 comment中所有的数据。
(2 beforelnvocation属性 :
beforeInvocation属性表示 是否在方法执行之前 进行 缓存清除 ,默认值为 false ( 即 默认在执行方法后 再 进行 缓存清除)。
例如 : @CacheEvict ( cacheNames = “comment” , beforelnvocation true) 表示 会 在方法执行之前 进行 缓存清除。需要注意的是 :
如果将@CacheEvict 注解的beforelnvocation属性 设置为 true,会 存在一定的弊端,例如在进行数据删除的方法中发生了 异常,这会导致实际数据并没有被删除,但是缓存数据却被提前清除了。
@CacheEvict 注解 代码例子如 :
CommentController.java :
@Controller public class CommentController { @Autowired private CommentService commentService; /** * 根据id查询数据 ( 将在service层中添加"缓存管理" ) */ @GetMapping("/delete/{id}") public void delete(@PathVariable("id") Integer id) { //使用@PathVariable路径变量注解来获取“路径下的变量” commentService.delete(id); } }
CommentService.java :
@Service //将该类加入到ioc容器中 public class CommentService { @Autowired private CommentRepository commentRepository; /** * 根据id来删除数据 */ @CacheEvict(cacheNames = "comment",key = "#id") //删除数据库的数据后,也删除“缓存空间”中的对应的“缓存数据” public void delete(Integer id) { System.out.println("数据库的数据删除成功,后清除缓存数据"); } }
CommentRepository.java :
public interface CommentRepository extends JpaRepository<Comment,Integer> { }
2.5 @Caching注解 ( 作用于"类" 或 “方法” )
如果 处理复杂规则 的 数据缓存 可以使用 @Caching 注解,该注解 作用于 **类**或者 方法。
@Caching注解包含 cacheable、put 和 evict 三个属性,这三个属性 作用等同于 @Cacheable注解、@CachePut注解、@CacheEvict注解 ,实例代码如下 :
/** * 使用@Caching注解 */ @Caching( cacheable = {@Cacheable(cacheNames = "comment", key = "#id")}, put = {@CachePut(cacheNames = "comment", key = "#result.author")} ) public Comment findCommentWithAuthor(Long id) { // ... 方法实现,查找评论和作者 }
2.6 @CacheConfig注解 ( 作用于"类" , 为该类下 的 “缓存注解” 设置 “公共属性” )
@CacheConfig注解作用于类,主要用于 统筹管理类中所有使用@Cacheable、CacheEvict、@CacheEvict 注解标注的 方法中的公共属性,这些公共属性包括 : cacheNames、keyGenerator、cacheManager和cacheResolver,示例代码如下 :
CommentService.java :
@Service //将该类加入到ioc容器中 /** * 统筹该类下的所有使用@Cacheable()注解、@CachePut()注解、@CacheEvict()注解 , 比如此处统筹其下的方法的“缓存空间”都统一设置为"comment" */ @CacheConfig(cacheNames = "comment") public class CommentService { @Autowired private CommentRepository commentRepository; @Cacheable(cacheNames = "comment",key = "comment_id") public Comment findById(int comment_id) { Optional<Comment> optional = commentRepository.findById(comment_id); if (optional.isPresent()) { return optional.get(); } return null; } }
上述代码中,CommentService 类上标注了 @CacheConfig注解,同时使用 cacheNames属性将缓存空间统一设置为 comment,这样在 该类中所有方法 上使用 缓存注解时 可以省略相应的cacheNames属性。
需要说明的是 :如果在类上使用了 @CacheConfig 注解定义了某个属性(例如 cacheNames) 同时又在该类方法中使用缓存注解定义了 相同的属性,那么该属性值会使用“就近原则”,以方法上注解中的属性值为准。