• Spring Boot | Spring Boot 默认 “缓存管理“ 、Spring Boot “缓存注解“ 介绍



    在这里插入图片描述

    作者简介 :一只大皮卡丘,计算机专业学生,正在努力学习、努力敲代码中! 让我们一起继续努力学习!

    该文章参考学习教材为:
    《Spring Boot企业级开发教程》 黑马程序员 / 编著
    文章以课本知识点 + 代码为主线,结合自己看书学习过程中的理解和感悟 ,最终成就了该文章

    文章用于本人学习使用 , 同时希望能帮助大家。
    欢迎大家点赞👍 收藏⭐ 关注💖哦!!!

    (侵权可联系我,进行删除,如果雷同,纯属巧合)


    • 缓存分布式系统中的重要组件,主要 解决数据厍数据高并发访问问题。在实际开发中,尤其是用户访问量较大网站,为了提高服务器访问性能减少数据库的压力提高用户体验,使用 缓存显得尤为重要Spring Boot缓存提供了良好的支持

    一、Spring Boot 默认 “缓存” 管理 :

    • Spring框架支持透明地应用程序添加缓存并对缓存进行管理,其管理缓存核心是将缓存 应用于操作数据的方法中,从而 减少操作 数据的次数同时不会对程序本身造成任何干扰
    • Spring Boot继承了Spring框架缓存管理功能,通过使用 @EnableCaching注解开启基于注解缓存支持Spring Boot可以启动缓存管理自动化配置。

    1.1 基础环境搭建

    • 使用缓存主要目的减少数据库数据访问压力提高用户体验,下面代码例子将结合数据库访问操作Spring Boot缓存管理讲行演示说明
    ① 准备数据

    先创建了一个 数据库springbootdata,然后创建了两个表 t_articlet_comment ,并向表中插入数据。
    其中评论表t_commenta_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 +
                      '}';
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
    ④ 编写 “操作数据库” 的 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);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    ⑤ 编写 “业务操作列” 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);
          }
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
    ⑥ 编写 “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
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    ⑦ 项目测试 ( 实际开发中的"问题突显",用 “缓存技术” 能解决这个问题 )
    • 启动项目项目启动成功后,在浏览器上访问 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);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    (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类型的对象
            Optional<Comment> optional = commentRepository.findById(comment_id);
            if (optional.isPresent()) { //用于检查Optional对象"是否包含"一个"值"
                return  optional.get(); //返回这个Optional对象包含的"值"
            }
            return null;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    上述代码中,在CommentService类中的 findByld( int comment_id )方法上添加了 @Cacheable( )注解,该注解作用查询结果 : Comment 存放
    Spring Boot默认缓存 中 名称为 : comment名称空间 中。

    对应缓存唯一标识 ( 即缓存数据对应的主键 key ) 默认方法参数comment_id的值

    (3) Spring Boot默认缓存测试
    • 启动项目,通过浏览器继续访问 “http:/localhost:8080/get/1” 查询id1用户评论信息。此时不论浏览器刷新多少次访问同一个用户评论信息页面的查询结果会显示同一条数据,但**重点**是 后端不用进行多次数据库操作了,此时后端只进行了一次数据库操作


      在这里插入图片描述


      在这里插入图片描述

    二、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);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

    2.2 @Cacheable( ) 注解 ( 作用于"类" 或 “方法”,通常用在 “数据查询” 功能的 “方法” 上 , 将 “查询结果” 存进 “缓存空间” 中 )

    • @Cacheable 注解也是由 Spring 框架提供的,可以作用于类或方法通常用数据查询查法 上),用于 方法的查询结果 进行缓存存储 ( 将查询结果 存储在 “缓存空间” 中 )。

      @Cacheable 注解注解一般用在 select 功能方法上 ,当调用该方法时,先根据key去“缓存空间”中查询是否有“缓存数据”,如果,自然就用该“缓存数据” , 如果没有,则去数据库查询数据,然后将该数据 存储到 “缓存空间”中,作为“ 缓存数据”)

    • @Cacheable( )注解执行顺序是,先进行"缓存查询" ( 即在缓存空间查询是否有符合要求数据 ) ,如果 为空进行 “方法查询”如果不为空,则直接使用缓存数据 ( 此时不再进行"方法查询" )

    • @Cacheable( )注解多个属性,用于对“缓存存储”进行相关配置具体属性 及 说明 如下表所示

      属性名说明
      value / cacheNames指定缓存空间名称必配属性。这两个属性 二选一使用
      ps :
      如果 @Cacheable( ) 注解 只有 valuecacheNames( )一个属性时,则该属性的属性名可省略
      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类型的对象
            Optional<Comment> optional = commentRepository.findById(comment_id);
            if (optional.isPresent()) { //用于检查Optional对象"是否包含"一个"值"
                return  optional.get(); //返回这个Optional对象包含的"值"
            }
            return null;
         }
       }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30

    value / cacheNames 属性 ( 指定存储"缓存数据"的 “空间的名称” )

    • value属性cacheNames 属性 作用相同,用于 指定缓存名称空间 ( 指定“缓存空间”的 名称 ),可以同时指定多个名称空间(例如 @Cacheable ( cacheNames = {“comment1”,“comment2”} ) )。

    • 如果 @Cacheable( ) 注解 只有 valuecacheNames( )一个属性时,则该属性的属性名可省略,例如 @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类型的对象
            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;
        }
           }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35

    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表达式说明
      下表所示** :

      名称位置描述
      methodNameroot对象当前被调用方法名#root.methodName
      methodroot对象当前被调用方法#root.method.name
      targetroot对象当前被调用的 目标对象实例#root.target
      targetClassroot对象当前被调用的目标对象#root.targetClass
      argsroot 对象当前 被调用的方法参数列表#root.args[0]
      cachesroot对象当前 被调用的方法缓存列表#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类型的对象
            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类型的对象
            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类型的对象
            Optional<Comment> optional = commentRepository.findById(comment_id);
            if (optional.isPresent()) { //用于检查Optional对象"是否包含"一个"值"
                return  optional.get(); //返回这个Optional对象包含的"值"
            }
            return null;
        }
       }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63

    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;
          }
    
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    keyGenerator属性 ( keyGenerator属性 : 指定生成keykey值生成器",该属性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类型的对象
            Optional<Comment> optional = commentRepository.findById(comment_id);
            if (optional.isPresent()) { //用于检查Optional对象"是否包含"一个"值"
                return  optional.get(); //返回这个Optional对象包含的"值"
            }
            return null;
          }
        }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    cacheManager / cacheResolver属性

    • cacheManager ( 缓存管理器 )和 cacheResolver ( 缓存解析器 )属性分别用于指定 缓存管理器缓存解析器,这两个属性也是

      二选一使用,默认情况不需要配置,如果存在多个cacheManager : 缓存管理器(如 RedisEhcache 等 ),可以 使用这两个属性分别指定

    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类型的对象
            Optional<Comment> optional = commentRepository.findById(comment_id);
            if (optional.isPresent()) { //用于检查Optional对象"是否包含"一个"值"
                return  optional.get(); //返回这个Optional对象包含的"值"
            }
            return null;
           }
        }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22

      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;
          }
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

    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类型的对象
            Optional<Comment> optional = commentRepository.findById(comment_id);
            if (optional.isPresent()) { //用于检查Optional对象"是否包含"一个"值"
                return  optional.get(); //返回这个Optional对象包含的"值"
            }
            return null;
         }
       }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24

    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;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26

      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;
         }
       }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33

      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> { 
      
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

    2.4 @CacheEvict 注解 ( 作用于"类" 或 “方法”,通常用在 “数据删除” 方法上 ,在”删除数据库中 数据“ 后,进行删除 “缓存数据” )

    • @CacheEvict 注解是由Spring框架提供的,可以作用于 方法(通常用在 “数据删除” 方法上),该 注解的作用"删除" 缓存数据

      @CacheEvict 注解默认执行顺序是 : 先进行方法调用然后清除缓存。( 默认先执行方法进行数据库删除然后再清除 “缓存”)

      @CacheEvict注解提供了多个属性,这些属性@Cacheable注解属性基本相同除此之外@CacheEvic注解 额外提供了两个特殊属性 : allEntriesbeforelnvocation其说明如下 :


      (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);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      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("数据库的数据删除成功,后清除缓存数据");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      CommentRepository.java

      public interface CommentRepository extends JpaRepository<Comment,Integer> {
      
      }
      
      • 1
      • 2
      • 3

    2.5 @Caching注解 ( 作用于"类" 或 “方法” )

    • 如果 处理复杂规则数据缓存 可以使用 @Caching 注解注解 作用于 ****或者 方法

    • @Caching注解包含 cacheableputevict 三个属性,这三个属性 作用等同于 @Cacheable注解@CachePut注解@CacheEvict注解实例代码如下 :

      /**
      * 使用@Caching注解
      */
      @Caching(
          cacheable = {@Cacheable(cacheNames = "comment", key = "#id")},
          put = {@CachePut(cacheNames = "comment", key = "#result.author")}
      )
      public Comment findCommentWithAuthor(Long id) {
          // ... 方法实现,查找评论和作者
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

    2.6 @CacheConfig注解 ( 作用于"类" , 为该类下 的 “缓存注解” 设置 “公共属性” )

    • @CacheConfig注解作用于,主要用于 统筹管理类中所有使用@CacheableCacheEvict@CacheEvict 注解标注方法中的公共属性,这些公共属性包括 : cacheNameskeyGeneratorcacheManagercacheResolver示例代码如下 :

      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;
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19

    上述代码中,CommentService 类上标注了 @CacheConfig注解,同时使用 cacheNames属性缓存空间统一设置为 comment,这样在 该类中所有方法 上使用 缓存注解时 可以省略相应cacheNames属性


    需要说明的是 :如果在类上使用了 @CacheConfig 注解定义了某个属性(例如 cacheNames同时又在该类方法中使用缓存注解定义相同的属性,那么该属性值会使用“就近原则”,方法上注解中的属性值为准

  • 相关阅读:
    Springboot项目升级2.2.x升至2.7.x
    coudn‘t deserialize object in variable ‘entity‘.获取流程变量无法反序列化问题
    websocket在java中的使用教程
    负载均衡器 OpenELB ARP 欺骗技术解析
    K8S云计算系列-(2)
    docker查看jvm内存占用
    云原生微服务架构图
    金和OA SQL注入漏洞
    【C++】走进 ⌈ 类和对象 ⌋ 的核心 - 感受C++的精华 _ 剖析默认成员函数 | 构造函数 | 析构函数 | 拷贝构造函数 | 赋值运算符重载
    大二Web课程设计——张家界旅游网站设计与实现(HTML+CSS+JavaScript)
  • 原文地址:https://blog.csdn.net/m0_70720417/article/details/137943481