目录:
- 一、Spring Boot 默认 "缓存" 管理 :
- 1.1 基础环境搭建
- ① 准备数据
- ② 创建项目
- ③ 编写 "数据库表" 对应的 "实体类"
- ④ 编写 "操作数据库" 的 Repository接口文件
- ⑤ 编写 "业务操作列" Service文件
- ⑥ 编写 "application.properties配置文件"
- ⑦ 项目测试 ( 实际开发中的"问题突显",用 "缓存技术" 能解决这个问题 )
- 1.2 Spring Boot "默认缓存体验"
- (1) 使用 "@EnableCaching" 注解 开始 “缓存管理”
- (2) 使用 "@Cacheable( )" 注解对 "数据操作方法" 进行 “缓存管理” ( 将该注解放在service类的“操作方法”上,让其对"查询结果" 进行 “缓存” )
- (3) Spring Boot默认缓存测试
- 二、Spring Boot "缓存注解" 介绍 :
- @EnableCaching 注解 ( 用在 "主程序启动类" 上)
- @Cacheable( ) 注解 ( 作用于"类" 或 "方法",通常用在 "数据查询" 方法上 )
- CachePut 注解 ( 作用于"类" 或 "方法",通常用在 "数据更新" 方法上 )
- @CacheEvict 注解 ( 作用于"类" 或 "方法",通常用在 "数据删除" 方法上 )
- @Caching注解 ( 作用于"类" 或 "方法" )
- @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表的 “外键”)。springbootdata.sql
② 创建项目
创建项目,引入相关依赖 :
③ 编写 “数据库表” 对应的 “实体类”
编写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默认的基于注解 的 缓存管理,除此之外,还有更多的 缓存注解以及注解属性可以配置优化缓存管理。
@EnableCaching 注解 ( 用在 “主程序启动类” 上)
- @EnableCaching 注解 是由 Spring框架提供的,Spring Boot框架对该注解进行了 继承,该注解需要 配置在类上(在Spring Boot中 ,通常 配置 在 项目启动类上),用于 开启基于注解 的 缓存支持。
@Cacheable( ) 注解 ( 作用于"类" 或 “方法”,通常用在 “数据查询” 方法上 )
@Cacheable 注解也是由 Spring 框架提供的,可以作用于类或方法(通常用在数据查询查法上),用于 对方法的查询结果 进行 缓存存储 ( 将查询结果 存储在 “缓存空间” 中 )。
@Cacheable( )注解的执行顺序是,先进行"缓存查询" ( 即在缓存空间查询是否有符合要求的数据 ) ,如果 为空则 进行 “方法查询” ;如果不为空,则直接使用缓存数据 ( 此时则不再进行"方法查询" )
@Cacheable( )注解有多个属性,用于对“缓存存储”进行相关配置,具体属性 及 说明 如下表所示 :
属性名 说明 value / cacheNames 指定缓存空间的名称,必配属性。这两个属性 二选一使用。
ps :
如果 @Cacheable( ) 注解 只有 value 或 cacheNames( ) 这 一个属性时,则该属性的属性名可省略。key 指定==缓存数据的 key== ( 该“缓存”的 唯一标识),默认使用 方法参数值,可以使用SpEL表达式。 keyGenerator 指定 缓存数据 的 key 的 生成器,与key属性二选一使用。 cacheManager 指定 缓存管理器。 cacheResolver 指定 缓存解析器,与 cacheManager属性二选一使用。 condition 指定在符合某条件下,“进行” 数据缓存。 unless 指定在符合某条件下,“不进行” 数据缓存。 sync 指定 是否使用 “异步缓存”。默认 false。
value / cacheNames 属性 ( 指定存储"缓存数据"的 “空间的名称” )
- value属性 和 cacheNames 属性 作用相同,用于 指定缓存的名称空间 ( 指定“缓存空间”的 名称 ),可以同时指定多个名称空间(例如 @Cacheable ( cacheNames = {“comment1”,“comment2”} ) )。
- 如果 @Cacheable( ) 注解 只有 value 或 cacheNames( ) 这 一个属性时,则该属性的属性名可省略,例如 @Cacheable(“comment”)指定了缓存的名称空间为 comment。
key 属性 ( 指定“缓存数据” 对应的 “key / 唯一标识” , 通过这个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
keyGenerator属性
keyGenerator属性与kev属性 本质作用 相同,都是用于指定缓存数据的key,只不过 keyGenerator 属性指定的不是具休的 key值,而是 key值的生成器规则,由其中 指定的生成器生成 具体的 key 。使用时, kevGenerator属性与key属性要二者选一。关于自定义key值生成器的定义,可以参考Sprina Boot默认配置类SimoleKavGenerator的定义方式。
cacheManager / cacheResolver属性
cacheManaaer和cacheResolver属性分别用于指定缓存管理器和缓存解析器,这两个属性也是二选一使用,默认情况不需要配置,如果存在多个缓存管理器(如 Redis、Ehcache 等 ),可以 使用这两个属性分别指定。
condition 属性
condition属性用于对数据进行 有条件的选择性存储,只有当指定条件为 true时才会对查询结果进行缓存,可以使用 SpEL表达式指定属性值。
例如 : @Cacheable(cacheNames = “comment ,condition =”#comment_id>10") 表示方法参数comment_id的 值大于10才会对结果数据进行缓存。
unless属性
unless属性的作用与 condition属性相反,当指定的条件为 true时,方法的返回值不会被缓存。unless属性可以使用SpEL表达式指定。
例如 :@Cacheable(cacheNames = "comment ,unless= “#result==nul”) 表示只有查询结果不为空才会对结果数据进行缓存存储。
sync属性
sync属性表示数据缓存过程中是否使用异步模式,默认值为 false。
CachePut 注解 ( 作用于"类" 或 “方法”,通常用在 “数据更新” 方法上 )
- @CachePut注解是由Spring框型提供的,可以作用于 类 或 方法 ( 通常用在"数据更新" 方法 上),该 注解的作用 是 : "更新"缓存数据 。
- CachePut 注解的执行顺序是 先进行 “方法调用”,然后将"方法结果" 更新到缓存中。
- @CachePut注解也提供了多个属性,这些属性与 @Cacheable注解的属性完全相同。
@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,会 存在一定的弊端,例如在进行数据删除的方法中发生了 异常,这会导致实际数据并没有被删除,但是缓存数据却被提前清除了。
@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 getComment(int comment_id) { return commentRepository.findById(comment_id).get(); //将从数据库中获得数据,作为"返回值"return }
上述代码中,根据id执行查询操作,并将查询到的Comment对象进行 缓存管理。从代码中可以看出,@Caching注解作用于 getComment( )方法上,并在 @Caching注解中使用了 cacheable 和 put两个属性 ,并且cacheable 和 put两个属性 嵌套引入 @Cacheable和 @CachePut两个注解,在两个注解中 分别使用#id 和 #result.author 缓存key的值。
@CacheConfig注解 ( 作用于"类" )
@CacheConfig注解作用于类,主要用于 统筹管理类中所有使用@Cacheable、CacheEvict、@CacheEvict 注解标注的 方法中的 公共属性,这些公共属性包括 : cacheNames、keyGenerator、cacheManager和cacheResolver,示例代码如下 :
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.CacheConfig; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; import org.springframework.stereotype.Controller; import org.springframework.stereotype.Service; import java.util.Optional; @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) 同时又在该类方法中使用缓存注解定义了 相同的属性,那么该属性值会使用“就近原则”,以方法上注解中的属性值为准。