Spring Cache详解

2023-10-27

第一节 Spring Cache介绍

1. Spring Cache 简介

从3.1开始,Spring引入了对Cache的支持。其使用方法和原理都类似于Spring对事务管理的支持。

Spring Cache是作用在方法上的,其核心思想是这样的:

当调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。所以在使用Spring Cache的时候要保证缓存的方法对于相同的方法参数要有相同的返回结果。

2. Spring Cache 注解

Spring为我们提供了几个注解来支持Spring Cache。其核心主要是@Cacheable@CacheEvict

2.1 @Cacheable

@Cacheable可以标记在一个方法上,也可以标记在一个类上。

  • 当标记在一个方法上时表示该方法是支持缓存的

  • 当标记在一个类上时则表示该类所有的方法都是支持缓存的

对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。需要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。

在支持Spring Cache的环境下,对于使用@Cacheable标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。

@Cacheable常用属性有4个:cacheNames、keyGenerator、condition 和 unless。

  • cacheNames

    cacheNames属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。其可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组

  • keyGenerator

    keyGenerator属性是用来指定缓存数据时使用的键,当没有指定该属性时,Spring将使用默认策略生成key

  • condition

    condition属性表示缓存的条件,这个条件与查询的参数有关。当有些查询条件不需要缓存时,可以通过condition来实现过滤。condition属性默认为空,表示将缓存所有的调用情形。

  • unless

    unless属性表示缓存的条件,这个条件与查询的结果有关。当有些查询结果不需要缓存时,可以通过unless来实现过滤。unless属性默认为空,表示将缓存所有的调用情形。

2.2 @CachePut

@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是:使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

2.3 @CacheEvict

@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。

@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。

  • value: value表示清除操作是发生在哪些Cache上的(对应Cache的名称)

  • key: key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key

  • condition: condition表示清除操作发生的条件。

  • allEntries: allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更有效率

  • beforeInvocation: 清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时机,当指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。

第二节 Spring Cache 的EL 表达式

Spring提供了一个root对象可以用来生成key。通过该root对象我们可以获取到以下信息。

属性名称 描述 示例
methodName 当前方法名 #root.methodName
method 当前方法 #root.method.name
target 当前被调用的对象 #root.target
targetClass 当前被调用的对象的class #root.targetClass
args 当前方法参数组成的数组 #root.args[0]
caches 当前被调用的方法使用的Cache #root.caches[0].name

使用方法参数时我们可以直接使用 #参数名 或者 #p参数index

第三节 Spring Cache应用

1. yml配置

spring:
  redis:
    jedis:
      pool:
        max-wait: -1
        max-active: 100
        max-idle: 10
        min-idle: 2
    host: 127.0.0.1
    port: 6379

2. Cache配置

package com.qf.spring.boot.redis.cache.config;
​
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
​
import java.time.Duration;
​
@Configuration
@EnableCaching //启用缓存
public class RedisConfiguration {
​
    private static final int defaultExpireTime = 300;
    /**
     * 缓存管理器
     *
     * @param connectionFactory
     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
        // 设置缓存管理器管理的缓存的默认过期时间
        .entryTtl(Duration.ofSeconds(defaultExpireTime))
         // 设置 key为string序列化
        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
         // 设置value为json序列化
        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(defaultCacheConfig)
                .build();
    }
}

3. 创建查询实体

package com.qf.spring.boot.redis.cache.model;
​
import lombok.Data;
import lombok.experimental.Accessors;
​
@Data
@Accessors(chain = true)
public class User {
​
    private String name;
​
    private String sex;
}

4. 创建业务层

package com.qf.spring.boot.redis.cache.service;
​
public interface UserService {
​
}
​
​
package com.qf.spring.boot.redis.cache.service.impl;
​
@Service
public class UserServiceImpl implements UserService {
    
}

5. @Cacheable注解

5.1 基本使用

package com.qf.spring.boot.redis.cache.service;
​
import com.qf.spring.boot.redis.cache.model.User;
​
import java.util.List;
​
public interface UserService {
​
    List<User> getAllUser();
}
​
​
package com.qf.spring.boot.redis.cache.service.impl;
​
import com.qf.spring.boot.redis.cache.model.User;
import com.qf.spring.boot.redis.cache.service.UserService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
​
import java.util.Arrays;
import java.util.List;
​
@Service
public class UserServiceImpl implements UserService {
​
​
    @Cacheable("userCache") //指定缓存的名称
    @Override
    public List<User> getAllUser() {
        return Arrays.asList(new User().setName("张三").setSex("男"), new User().setName("里斯").setSex("女"));
    }
}

编写测试案例

package com.qf.spring.boot.redis.cache;
​
import com.qf.spring.boot.redis.cache.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
​
@SpringBootTest
class SpringBootRedisCacheApplicationTests {
​
    @Autowired
    private UserService userService;
​
    @Test
    void cacheTest() {
        userService.getAllUser().forEach(System.out::println);
    }
​
}

执行测试,然后使用Redis Desktop Manager查看缓存结果。缓存确实存在,但缓存的键命名却不友好,可以使用 keyGenerator 属性来自定义缓存的键名称。

5.2 自定义键生成器

RedisConfiguration 中配置自定义键的生成器

@Bean
public KeyGenerator keyGenerator(){
    return (target, method, params) -> {
        StringBuilder builder = new StringBuilder();
        builder.append(target.getClass().getName()).append(".");//类名
        builder.append(method.getName()); //方法名
        if(params.length > 0){
            String paramInfo = Arrays.toString(params);//参数信息
            if(!"[]".equals(paramInfo.replace(" ","")))//参数信息不为空字符串
                builder.append(".").append(Arrays.toString(params));
        }
        return builder.toString();
    };
}

在缓存时,指定键生成器

@Cacheable(cacheNames = "userCache", keyGenerator = "keyGenerator")

再次测试,查看缓存结果

5.3 unless 应用

//unless属性的值如果为true,表示不进行缓存
//#result表示返回结果,因为结果是List集合,所以可以使用size()方法进行判断,如果结果小于5条,则不缓存
@Cacheable(cacheNames = "userCache", keyGenerator = "keyGenerator", unless = "#result.size() < 5")

删除 Redis 中的缓存,再次测试,查看缓存结果

5.4 condition 应用

新增业务接口及实现

package com.qf.spring.boot.redis.cache.service;
​
import com.qf.spring.boot.redis.cache.model.User;
​
import java.util.List;
​
public interface UserService {
​
    List<User> getAllUser();
​
    List<User> searchUsers(String name);
}
​
​
package com.qf.spring.boot.redis.cache.service.impl;
​
import com.qf.spring.boot.redis.cache.model.User;
import com.qf.spring.boot.redis.cache.service.UserService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
​
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
​
@Service
public class UserServiceImpl implements UserService {
​
​
    //unless属性的值如果为true,表示不进行缓存
    //#result表示返回结果,因为结果是List集合,所以可以使用size()方法进行判断,如果结果小于5条,则不缓存
    @Cacheable(cacheNames = "userCache", keyGenerator = "keyGenerator", unless = "#result.size() < 5")
    @Override
    public List<User> getAllUser() {
        return Arrays.asList(new User().setName("张三").setSex("男"), new User().setName("里斯").setSex("女"));
    }
​
    @Cacheable(cacheNames = "userCache", keyGenerator = "keyGenerator", condition = "!#p0.equals('管理员')")
    @Override
    public List<User> searchUsers(String name) {
        if("管理员".equals(name)) return Collections.singletonList(new User().setName("管理员").setSex("男"));
        return Arrays.asList(new User().setName(name + "1").setSex("其他"), new User().setName(name+"2").setSex("男"));
    }
}

修改测试案例

package com.qf.spring.boot.redis.cache;
​
import com.qf.spring.boot.redis.cache.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
​
@SpringBootTest
class SpringBootRedisCacheApplicationTests {
​
    @Autowired
    private UserService userService;
​
    @Test
    void cacheTest() {
        userService.getAllUser().forEach(System.out::println);
        userService.searchUsers("管理员").forEach(System.out::println);
    }
​
}

执行测试,查看缓存结果。

将查看条件由 "管理员" 调整为 "测试员",再次执行,查看缓存结果。

6. @CacheEvict 注解

6.1 新增业务接口及实现

int addUser(User user);
​
@CacheEvict(cacheNames = "userCache", allEntries = true)
@Override
public int addUser(User user) {
    return 1;
}

6.2 编写测试案例

@Test
void cacheEvictTest(){
    userService.addUser(new User().setName("新增人员").setSex("男"));
}

6.3 beforeInvocation 属性测试

7. 数据库访问测试

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring Cache详解 的相关文章

  • 如何在 Eclipse 中用阿拉伯语读写

    我在 eclipse 中编写了这段代码来获取一些阿拉伯语单词 然后打印它们 public class getString public static void main String args throws Exception PrintS
  • 检查双精度值的等于和不等于条件

    我在比较两者时遇到困难double values using and 我创建了 6 个双变量并尝试进行比较If健康 状况 double a b c d e f if a b c d e f My code here in case of t
  • 将链接对象转换为流或集合

    我想迭代堆栈跟踪 堆栈跟踪由可抛出对象组成 其 getCause 返回下一个可抛出对象 最后一次调用 getCause 返回 null 示例 a gt b gt null 我尝试使用 Stream iterable 这会导致 NullPoi
  • jdbc-initialize-database 找不到脚本文件 - DataAccessResourceFailureException

    我无法让我的 Spring Web 应用程序找到我的脚本 我已经配置了 jdbc 命名空间 我已经写好了脚本 但是 我不明白为什么无论我把脚本放在哪里 都找不到它们 我特别将文件夹标记为 类文件夹 我把它们和我所有的罐子放在同一个文件夹里
  • 如何从另一个xml文件动态更新xml文件?

    我想从另一个 xml 文件更新 xml 文件 我使用了一个 xml 文件 如下所示 one xml
  • Google Inbox 类似 RecyclerView 项目打开动画

    目前 我正在尝试实现 Google Inbox 例如RecyclerView行为 我对电子邮件打开动画很好奇 我的问题是 该怎么做 我的意思是 他们使用了哪种方法 他们用过吗ItemAnimator dispatchChangeStarti
  • Java 重写 hashCode() 得到 StackOverflowError

    所以我不太熟悉重写 hashCode 并且我似乎在 hashCode 方法中以某种方式进行了一些无限递归 这是我的场景 我有一个 DuplicateCache 类 它是一个缓存对象 用于检查系统中的重复对象 我有一个静态内部类 Duplic
  • Android - 除了普通 SSL 证书之外还验证自签名证书

    我有一个通过 SSL 调用 Web 服务的 Android 应用程序 在生产中 我们将拥有由受信任的 CA 签名的普通 SSL 证书 但是 我们需要能够支持自签名证书 由我们自己的 CA 签名 我已经成功实施了接受自签名证书的建议解决方案
  • 场景生成器删除 fxml 文件中的导入

    我使用场景构建器 Gluon Scene Builder JavaFX Scene Builder 8 1 1 来创建应用程序的 UI 并使用 Eclipse 开发 JavaFX 现在 每次我在场景生成器中保存某些内容时 它都会从 fxml
  • 使用 Java 在浏览器中下载 CSV 文件

    我正在尝试在 Web 应用程序上添加一个按钮 单击该按钮会下载一个 CSV 文件 该文件很小 大小仅约 4KB 我已经制作了按钮并附加了一个侦听器 文件也准备好了 我现在唯一需要做的就是创建单击按钮时下载 csv 文件的实际事件 假设 fi
  • 如何将 XMP XML 块序列化为现有的 JPEG 图像?

    我有许多 JPEG 图像 其中包含损坏的 XMP XML 块 我可以轻松修复这些块 但我不确定如何将 固定 数据写回图像文件 我目前正在使用 JAVA 但我愿意接受任何能让这项任务变得容易的事情 这是目标关于 XMP XML 的另一个问题
  • Java替换特定字符

    这是我在这个网站上的第一个问题 所以我会尽量不要成为一个十足的菜鸟 我目前正在用java 创建刽子手游戏 所以我问你的问题是我们是否被赋予了 幽灵 这个词 并将 Ghost 替换为 hiddenWord ghost length for i
  • 使用 Guava 联合两个 ImmutableEnumSets

    我想联合两个ImmutableEnumSets来自番石榴 这是我的尝试 public final class OurColors public enum Colors RED GREEN BLUE YELLOW PINK BLACK pub
  • 参数动态时如何构建 JPQL 查询?

    我想知道是否有一个好的解决方案来构建基于过滤器的 JPQL 查询 我的查询太 富有表现力 我无法使用 Criteria 就像是 query Select from Ent if parameter null query WHERE fiel
  • 具有多种值类型的 Java 枚举

    基本上我所做的是为国家编写一个枚举 我希望不仅能够像国家一样访问它们 而且还能够访问它们的缩写以及它们是否是原始殖民地 public enum States MASSACHUSETTS Massachusetts MA true MICHI
  • 覆盖 MATLAB 默认静态 javaclasspath 的最佳方法

    MATLAB 配置为在搜索用户可修改的动态路径之前搜索其静态 java 类路径 不幸的是 静态路径包含相当多非常旧的公共库 因此如果您尝试使用新版本 您可能最终会加载错误的实现并出现错误 例如 静态路径包含 google collectio
  • 从 Java 日历迁移到 Joda 日期时间

    以前 当我第一次设计股票应用相关软件时 我决定使用java util Date表示股票的日期 时间信息 后来我体会到了大部分方法java util Date已弃用 因此 很快 我重构了所有代码以利用java util Calendar 然而
  • Apache Commons CLI:替代已弃用的 OptionBuilder?

    IntelliJ 显示此示例代码中不推荐使用 OptionBuilderhttp commons apache org proper commons cli usage html http commons apache org proper
  • 如何在keycloak中动态编辑standalone.xml文件

    我正在尝试通过 docker 编辑standalone xml 并尝试添加 但 keycloak 正在使用它standalone xml 但我可以看到standalone xml 文件中的更改 我需要在standalone xml 文件中添
  • 使用 Java 从 S3 上的文件在 S3 上创建 zip 文件

    我在 S3 上有很多文件 需要对其进行压缩 然后通过 S3 提供压缩文件 目前 我将它们从流压缩到本地文件 然后再次上传该文件 这会占用大量磁盘空间 因为每个文件大约有 3 10MB 而且我必须压缩多达 100 000 个文件 所以一个 z

随机推荐

  • Python 数据分析测试6 之 分组数据的 柱状图显示

    Time 2020 08 06 Author Xiaohong 运行环境 OS Windows 10 Python 3 7 功能 导入1份文件 以鞋部位分组 求得记录数 及累计缴库量 效果如下 1 以鞋部位分组 求得记录数 2 以鞋部位分组
  • 华为OD机试真题- 篮球比赛-2023年OD统一考试(B卷)

    题目描述 篮球 5V5 比赛中 每个球员拥有一个战斗力 每个队伍的所有球员战斗力之和为该队伍的总体战斗力 现有10个球员准备分为两队进行训练赛 教练希望2个队伍的战斗力差值能够尽可能的小 以达到最佳训练效果 给出10个球员的战斗力 如果你是
  • 学习STM32 SPI学习与应用

    认识一下SPI SPI的全称是 Serial Peripheral Interface 意为串行外围接口 SPI接口主要应用在EEPROM FLASH 实时时钟 AD转换器 还有数字信号处理器和数字信号解码器之间 SPI是一种高速的 全双工
  • 中大型企业选择云服务器还是传统服务器自建机房成本核算

    中大型企业自建机房还是选择阿里云服务器或腾讯云服务器 企业上云是趋势 大型企业自己购买服务器自建机房还是使用云服务器更省钱 自建机房一次性投入 但是云服务器每年都需要续费 云服务器吧从机房部署 容灾备份 安全可靠 运维及成本等方面来全方位对
  • Flask学习笔记(十三)数据库基本操作

    数据库基本操作 在Flask SQLAlchemy中 插入 修改 删除操作 均由数据库会话管理 会话用db session表示 在准备把数据写入数据库前 要先将数据添加到会话中然后调用commit 方法提交会话 数据库会话是为了保证数据的一
  • vue项目中自动拉取更新Iconfont(阿里巴巴图标库)

    在vue项目中使用 iconfont图标库 网上的栗子很多 这边就随手给一个 点这里 上面的解决了 那我就很苦恼 我每次添加 或删除 或更新图标库 需要重新下载 自己手动去覆盖吗 我是拒绝的 so 自动覆盖就很有必要了 贴一下我的iconf
  • Linux通过Nginx部署Vue项目

    Vue Springboot前后端项目分离开发 我们在部署的时候就需要将两者分开来部署 vue部署 由于我们是通过Linux nginx的方式来部署vue 因此在你的linux中需要安装nginx nginx的安装方式不多说 直接解压ngi
  • 网络安全期末复习

    前言 本文用于网络安全课期末复习资料 都是从老师上课讲的ppt和课本总结的 可能不太全 后面有一部分习题 填空和简答 如果需要word版可以找我要 目录 知识点 信息安全属性 网络防御模型 密码体制 数字证书与公钥基础设施 PKI 防火墙
  • 油盐微服务——声明式服务调用Feign

    文章目录 Feign快速入门 参数绑定 继承特性 Ribbon配置 Hystrix配置 对于前面的Ribbon和Hystrix的介绍 Spring Cloud Feign基于Netflix Feign实现 封装整合了以上两个工具 来简化开发
  • Apache mod_wsgi模块简介

    Apache HTTP服务器的mod wsgi扩展模块 实现了Python WSGI标准 可以支持任何兼容Python WSGI标准的Python应用 出于安全的考虑 建议使用mod wsgi 3 5及以后的版本 最新版本是2017年1月发
  • [C/C++]内存管理,对内存进行操作

    目录 一 内存结构 二 内存拷贝函数 三 栈空间与堆空间 四 变量的四种存储类型 五 函数返回值使用指针 六 常见错误总结 个人主页 北 海 CSDN新晋作者 欢迎 点赞 评论 收藏 收录专栏 C C 希望作者的文章能对你有所帮助 有不足的
  • Python写2048小游戏全部代码及所用图片

    鉴于有很多朋友要图片 把整个项目上传了 自行去取吧 资源地址 https download csdn net download qq 44651842 20009590 游戏效果 游戏用到的图片 数字图片都是90像素的正方形 游戏代码 20
  • python外星人入侵添加音效_Python 项目实践一(外星人入侵)第一篇

    python断断续续的学了一段实践 基础课程终于看完了 现在跟着做三个小项目 第一个是外星人入侵的小游戏 一 Pygame pygame 是一组功能强大而有趣的模块 可用于管理图形 动画乃至声音 让你轻松的开发出想要的游戏 二 安装Pyga
  • sqlite的事务和锁,很透彻的讲解 【转】

    原文 sqlite的事务和锁 http 3y uu456 com bp 877d38906bec097sf46se240 1 html 事务 事务定义了一组SQL命令的边界 这组命令或者作为一个整体被全部执行 或者都不执行 事务的典型实例是
  • 概念题3

    SaaS的意思是软件服务化 Software as a Service Windows上的共享文件传输使用哪个端口137 138 139 445 我国PCM数字设备间的传输接口码型是HDB3 调制解调的目的是 MVC全名是Model Vie
  • 基于SSM的在线考试系统

    全网粉丝20W csdn特邀作者 博客专家 CSDN新星计划导师 java领域优质创作者 博客之星 掘金 华为云 阿里云 InfoQ等平台优质作者 专注于Java技术领域和毕业项目实战 文末获取项目下载方式 一 项目背景介绍 在线考试借助于
  • 全网最详细,Fiddler抓包实战 - 网页浏览器https请求(超详细)

    目录 导读 前言 一 Python编程入门到精通 二 接口自动化项目实战 三 Web自动化项目实战 四 App自动化项目实战 五 一线大厂简历 六 测试开发DevOps体系 七 常用自动化测试工具 八 JMeter性能测试 九 总结 尾部小
  • Qt-D指针和Q指针及使用

    阅读Qt的源代码的时候 我们经常看Q D Q Q Q DECLARE PRIVATE Q DECLARE PUBLIC这几个宏 这几个宏是干什么用的呢 其实这几个宏就是实现D指针和Q指针的宏 D指针在Qt的源码中大量使用 根本目的在于解决二
  • Cocoapods方式导入OC库和swift库

    Cocoapods 是 iOS 应用的包管理程序 它简化了第三方库的导入并且将帮你处理库之间的依赖 在这个教程中 我们将会使用 cocoa pods 导入 Alamofire swift库 和AFNetworking OC库 这两个个第三方
  • Spring Cache详解

    第一节 Spring Cache介绍 1 Spring Cache 简介 从3 1开始 Spring引入了对Cache的支持 其使用方法和原理都类似于Spring对事务管理的支持 Spring Cache是作用在方法上的 其核心思想是这样的