Redis篇--常见问题篇8--缓存一致性3(注解式缓存Spring Cache)

1、概述

Spring Cache是Spring框架提供的一个缓存抽象层,旨在简化应用程序中的缓存管理。通过使用Spring Cache,开发者可以轻松地将缓存机制集成到业务逻辑中,而无需关心具体的缓存实现细节。

Spring Cache支持多种缓存提供者(如EhCache、Caffeine、Redis、Hazelcast等),并且提供了统一的API来操作缓存。

Spring Cache的主要目标是减少对后端数据源(如数据库、远程服务等)的访问次数,从而提高应用的性能和响应速度。它通过缓存计算结果或查询结果,避免重复执行相同的业务逻辑或数据库查询。

2、Spring Cache的核心概念

(1)、CacheManager

CacheManager是Spring Cache的核心接口之一,负责管理多个Cache实例。每个Cache实例对应一个缓存区域(cache region),用于存储特定类型的缓存数据。
CacheManager负责创建、获取和管理这些缓存区域。

常见的 CacheManager 实现包括:

  • ConcurrentMapCacheManager:基于内存的缓存,默认使用ConcurrentHashMap。
  • EhCacheCacheManager:基于EhCache的缓存。
  • CaffeineCacheManager:基于Caffeine的缓存。
  • RedisCacheManager:基于Redis的分布式缓存。
  • HazelcastCacheManager:基于Hazelcast的分布式缓存。

(2)、Cache

Cache接口表示一个具体的缓存区域,负责存储和检索缓存数据。每个Cache实例通常与一个命名空间相关联,例如user-cache或product-cache。
Cache提供了基本的缓存操作,如get()、put()和evict()。

(3)、@Cacheable

@Cacheable注解用于标记需要缓存的方法。当方法被调用时,Spring Cache会首先检查缓存中是否存在相同参数的结果。如果存在,则直接返回缓存中的结果,此时不会执行方法中的代码;如果不存在,则执行方法并将结果存入缓存。
示例:

@Cacheable(value = "user-cache", key = "id")
public User getUserById(Long id) {// 查询数据库或远程服务return userRepository.findById(id).orElse(null);
}

解释:

  • value:指定缓存的名称,即缓存区域的名称。你可以为不同的业务逻辑配置不同的缓存区域。
  • key:指定缓存的键生成策略。默认情况下,Spring Cache会根据方法参数自动生成缓存键。你也可以使用SpEL(Spring Expression Language)来定义更复杂的键生成规则。
  • condition:指定缓存条件。只有当条件为true时,才会将结果存入缓存。
  • unless:指定不缓存的条件。即使方法执行成功,只有当条件为false时,才会将结果存入缓存。

@Cacheable 注意的点:
1、当方法中抛出异常时,不会缓存任何数据。
2、当方法中返回null时,默认情况下不会缓存null,但是可以通过配置cache-null-values属性实现缓存null。
例如:

@Cacheable(value = "user-cache", key = "id", cache-null-values = true)
public User getUserById(Long id) {// 查询数据库或远程服务return userRepository.findById(id).orElse(null);
}

3、当方法中存在不确定因素时,不建议使用@Cacheable注解。
如:方法中你返回了对象,同时也打印数据信息到日志中。第一次请求会按照你的预期执行。但第二次请求会直接走缓存,不会走方法,从而造成无法打印日志。
所以在使用@Cacheable注解时,方法内建议使用最少且确定的查询操作。可以mapper文件中执行,如果有其他操作建议放在之前的service中去完成。

4、假设我们只想缓存非空的用户信息,使用unless示例。
注意unless是指定不缓存的条件,这里设置result==null,实际会缓存result!=null的数据。
示例:

@Cacheable(value = "user-cache", key = "id", unless = "result == null")
public User getUserById(Long id) {// 查询数据库或远程服务return userRepository.findById(id).orElse(null);
}

5、带条件的缓存condition
假设我们只在用户年龄大于18岁时缓存用户信息
示例:

@Cacheable(value = "user-cache", key = "id", condition = "id > 18")
public User getUserById(Long id) {// 查询数据库或远程服务return userRepository.findById(id).orElse(null);
}

6、指定缓存key的格式
可以使用SpEL表达式来组合多个参数,生成更复杂的缓存键
示例:

@Cacheable(value = "user-cache", key = "id + '_' + username")
public User getUserByIdAndUsername(Long id, String username) {// 查询数据库或远程服务return userRepository.findByIdAndUsername(id, username).orElse(null);
}

(4)、@CachePut

@CachePut注解用于更新缓存中的数据。与@Cacheable不同,@CachePut总是会执行方法,并将结果存入缓存。它通常用于更新缓存中的数据,而不影响方法的正常执行。
示例:

@CachePut(value = "user-cache", key = "user.id")
public User updateUser(User user) {// 更新数据库或远程服务userRepository.save(user);return user;
}

解释:

  • value:指定缓存的名称。
  • key:指定缓存的键生成策略。
  • condition:指定缓存条件。

(5)、@CacheEvict

@CacheEvict注解用于清除缓存中的数据。它可以用于删除单个缓存条目,也可以清除整个缓存区域。它通常用于在数据发生变更时清理过期的缓存数据。
示例:

@CacheEvict(value = "user-cache", key = "id")
public void deleteUser(Long id) {// 删除数据库或远程服务中的数据userRepository.deleteById(id);
}

解释:

  • value:指定缓存的名称。
  • key:指定要清除的缓存条目的键。
  • allEntries:如果设置为true,则清除整个缓存区域中的所有条目。
  • beforeInvocation:如果设置为 true,则在方法执行之前清除缓存;否则,在方法执行之后清除缓存。

(6)、@Caching

@Caching注解允许你组合多个缓存注解(如@Cacheable、@CachePut和@CacheEvict),以便在同一方法上应用多个缓存策略。
示例1:
保存user-cache中的键,同时删除user-stats-cache中的键

@Caching(cacheable = @Cacheable(value = "user-cache", key = "id"),evict = @CacheEvict(value = "user-stats-cache", key = "id")
)
public User getUserById(Long id) {// 查询数据库或远程服务return userRepository.findById(id).orElse(null);
}

示例2:
一条信息保存到多个缓存区域。

@Caching(cacheable = {@Cacheable(value = "user-cache", key = "id"),  // 缓存到 user-cache@Cacheable(value = "admin-cache", key = "id", condition = "user.isAdmin()")})public User getUserById(Long id) {return userRepository.findById(id).orElse(null);}

3、Spring Cache的工作原理

1、拦截方法调用:当调用带有缓存注解的方法时,Spring AOP会拦截该方法的执行。
2、检查缓存:Spring Cache会根据注解中的配置(如@Cacheable)检查缓存中是否存在相同参数的结果。

  • 如果存在缓存结果,则直接返回缓存中的数据,跳过方法的实际执行。
  • 如果不存在缓存结果,则继续执行方法。
    3、执行方法:如果缓存中没有找到结果,Spring Cache会执行方法,并将结果存入缓存。
    4、更新或清除缓存:根据注解的配置(如@CachePut或@CacheEvict),Spring Cache可能会在方法执行前后更新或清除缓存。

4、集成Spring Cache

(1)、启用缓存支持

要在Spring Boot项目中启用缓存支持,首先需要在主类或配置类上添加@EnableCaching注解。
示例:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;@SpringBootApplication
@EnableCaching
public class CacheApplication {public static void main(String[] args) {SpringApplication.run(CacheApplication.class, args);}
}

(2)、选择缓存提供者

Spring Cache支持多种缓存提供者。你可以根据需求选择合适的缓存实现。这里以Redis为例。
首先,添加 Redis 的依赖:

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

然后,配置Redis缓存管理器(application.yml):

spring:cache:type: redis    // 指定spring Cache的方法为Redis(重点)redis:host: localhostport: 6379timeout: 5000   连接超时时间lettuce:pool:max-active: 8max-idle: 8min-idle: 0redis:cache:time-to-live: 60000   默认缓存过期时间为 60 秒(毫秒)

(3)、编写配置类

这里配置类主要是为了设置合理的序列化,以及针对不同域的key设置不同的过期时间。

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
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.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.*;import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.time.Duration;@Slf4j
@Configuration
@EnableCaching
@EnableConfigurationProperties(RedisProperties.class)
public class CacheConfig extends CachingConfigurerSupport {private static final FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);//缓存管理器。可以管理多个缓存//只有CacheManger才能扫描到cacheable注解//spring提供了缓存支持Cache接口,实现了很多个缓存类,其中包括RedisCache。但是我们需要对其进行配置,这里就是配置RedisCache@Beanpublic CacheManager cacheManager(RedisConnectionFactory connectionFactory) {RedisCacheManager cacheManager = RedisCacheManager.RedisCacheManagerBuilder//Redis链接工厂.fromConnectionFactory(connectionFactory)//缓存配置 通用配置  默认存储一小时.cacheDefaults(getCacheConfigurationWithTtl(Duration.ofHours(1)))//配置同步修改或删除  put/evict.transactionAware()//对于不同的cacheName我们可以设置不同的过期时间
//                .withCacheConfiguration("app:",getCacheConfigurationWithTtl(Duration.ofHours(5))).withCacheConfiguration("user:",getCacheConfigurationWithTtl(Duration.ofHours(2))).build();return cacheManager;}//缓存的基本配置对象private   RedisCacheConfiguration getCacheConfigurationWithTtl(Duration duration) {return RedisCacheConfiguration.defaultCacheConfig()//设置key value的序列化方式// 设置key为String.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))// 设置value 为自动转Json的Object.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))// 不缓存null.disableCachingNullValues()// 设置缓存的过期时间.entryTtl(duration);}//缓存的异常处理@Bean@Overridepublic CacheErrorHandler errorHandler() {// 异常处理,当Redis发生异常时,打印日志,但是程序正常走log.info("初始化 -> [{}]", "Redis CacheErrorHandler");return new CacheErrorHandler() {@Overridepublic void handleCacheGetError(RuntimeException e, Cache cache, Object key) {log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);}@Overridepublic void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);}@Overridepublic void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);}@Overridepublic void handleCacheClearError(RuntimeException e, Cache cache) {log.error("Redis occur handleCacheClearError:", e);}};}/** @Override@Bean("myKeyGenerator")   // 自定义key的生成策略public KeyGenerator keyGenerator() {return new KeyGenerator() {@Overridepublic Object generate(Object target, Method method, Object... params) {StringBuffer sb = new StringBuffer();sb.append(target.getClass().getName());sb.append(method.getName());for (Object obj : params) {sb.append(obj.toString());}return sb.toString();}};
} 
**/
}

(4)、编写业务,使用注解

在业务需要的mapper文件中定义方法,参考上诉中的注解即使用示例就可以了。

5、Spring Cache实践建议

- 合理选择缓存提供者:根据应用场景选择合适的缓存提供者。对于单机应用,可以选择内存缓存(如Caffeine);对于分布式应用,可以选择分布式缓存(如Redis)。
- 避免缓存雪崩:缓存雪崩是指大量缓存条目同时失效,导致短时间内大量请求直接打到后端数据源。可以通过设置不同的TTL或使用缓存预热机制来避免缓存雪崩。
- 避免缓存穿透:缓存穿透是指查询的缓存键不存在,且后端数据源也没有对应的数据。可以通过设置默认值或使用布隆过滤器来避免缓存穿透。
- 避免缓存击穿:缓存击穿是指某个热点数据恰好在缓存失效时,大量请求同时访问该数据,导致后端数据源压力过大。可以通过加锁或使用互斥锁机制来避免缓存击穿。

  • 定期清理无效缓存:定期清理不再使用的缓存条目,避免缓存占用过多内存资源。

6、总结

Spring Cache是一个强大且灵活的缓存框架,能够显著提升应用的性能和响应速度。通过使用@Cacheable、@CachePut和@CacheEvict等注解,开发者可以轻松地将缓存机制集成到业务逻辑中,而无需关心具体的缓存实现细节。Spring Cache支持多种缓存提供者,开发者可以根据实际需求选择合适的缓存方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/64747.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java开发入门学习五-流程控制

流程控制语句 if&#xff0c; if...else&#xff0c; if..else if..else 与前端相同 略 switch case 与前端不同的是case不能使用表达式&#xff0c;使用表达式会报错 class TestSwitch {public static void main(String[] args) {// switch 表达式只能是特定的数据类型…

Vue 前端代码规范

在 Vue 前端开发中&#xff0c;遵循代码规范可以提高代码的可读性、可维护性和团队协作效率。以下是一些详细的 Vue 前端代码规范&#xff0c;涵盖了多个方面&#xff1a; ### 1. **项目结构** - **目录结构**:- src/ 目录下应包含 components/、views/、store/、router/、ass…

豆包MarsCode测评:编程效率再提升

豆包MarsCode测评&#xff1a;编程效率再提升 本文正在参与豆包MarsCode AI 编程体验家活动 随着人工智能技术的发展&#xff0c;编程的方式也在悄然发生变化。最近&#xff0c;豆包推出的 AI 编程工具 MarsCode 在开发者社区引发了不小的关注。这是一款支持多种主流编程语言…

FFmpeg 框架简介和文件解复用

文章目录 ffmpeg框架简介libavformat库libavcodec库libavdevice库 复用&#xff08;muxers&#xff09;和解复用&#xff08;demuxers&#xff09;容器格式FLVScript Tag Data结构&#xff08;脚本类型、帧类型&#xff09;Audio Tag Data结构&#xff08;音频Tag&#xff09;V…

frameworks 之 日志打印

frameworks 之 日志打印 1. 常见日志类型2. 动态日志ProtoLog 讲解 anddroid 日志打印类型。 1. 常见日志类型 android 日志类型分为 6 类 分别为 main,system, radio, events, crash, kernel 查看帮助指令 adb shell logcat --helpmain&#xff1a; 用于存储大多数应用和系统…

Unity开发哪里下载安卓Android-NDK-r21d,外加Android Studio打包实验

NDK下载方法&#xff08;是r21d,不是r21e, 不是abc, 是d版本呢) google的东西&#xff0c;居然是完全开源的 真的不是很多公司能做到&#xff0c;和那种伪搜索引擎是不同的 到底什么时候google才会开始造车 不过风险很多&#xff0c;最好不要合资&#xff0c;风险更大 Andr…

leetcode-128.最长连续序列-day14

为什么我感觉上述代码时间复杂度接近O(2n), 虽然有while循环&#xff0c;但是前面有个if判断&#xff0c;能进入while循环的也不多&#xff0c;while循环就相当于两个for循环&#xff0c;但不是嵌套类型的&#xff1a; 变量作用域问题&#xff1a;

Consul安装和使用:服务注册与发现

简介 Consul是一个开源的分布式服务发现和配置管理工具。它提供了一个分布式的、高可用的数据存储&#xff0c;可以用来存储键值对、配置数据、服务发现信息等。同时&#xff0c;Consul还提供了HTTP和DNS接口&#xff0c;可以用来查询服务、配置和健康状态等信息。 它具备以下…

Vue3之状态管理Vuex

Vuex作为Vue.js的官方状态管理库&#xff0c;在大型或复杂的前端项目中扮演着至关重要的角色。本文将从Vuex的原理、特点、应用场景等多个方面进行深入解析&#xff0c;并通过代码示例展示如何在Vuex中实现特定功能。 一、Vuex原理 Vuex是一个专为Vue.js应用程序开发的状态管…

34 Opencv 自定义角点检测

文章目录 cornerEigenValsAndVecscornerMinEigenVal示例 cornerEigenValsAndVecs void cornerEigenValsAndVecs(InputArray src, --单通道输入8位或浮点图像OutputArray dst, --输出图像&#xff0c;同源图像或CV_32FC(6)int blockSize, --邻域大小值int ape…

人工智能入门是先看西瓜书还是先看花书?

在人工智能入门时&#xff0c;关于先看《机器学习》&#xff08;西瓜书&#xff09;还是先看《深度学习》&#xff08;花书&#xff09;的问题&#xff0c;实际上取决于个人的学习目标和背景。 《机器学习》&#xff08;西瓜书&#xff09;由周志华教授撰写&#xff0c;是一本…

B 站数据库负责人赵月顺:助力海内外业务增长,百套 TiDB 的选型与运维实战

导读 B 站对 TiDB 的应用已相当广泛&#xff0c;被应用在了 包括视频观看、一键三连、发送弹幕、撰写评论、阅读漫画以及视频后端的存储等场景&#xff0c; 目前拥有近 100 套集群。 本文由 B 站数据库负责人赵月顺撰写&#xff0c; 详细介绍了 B 站面临业务增长选择 TiDB 的…

二九(vue2-05)、父子通信v-model、sync、ref、¥nextTick、自定义指令、具名插槽、作用域插槽、综合案例 - 商品列表

1. 进阶语法 1.1 v-model 简化代码 App.vue <template><!-- 11-src-下拉封装 --><div class"app"><!-- <BaseSelect :cityId"selectId" changeId"handleChangeId"></BaseSelect> --><!-- v-model 简化…

flask-admin+Flask-WTF 实现实现增删改查

背景&#xff1a; flask-adminflask-wtf在网上可以搜索到很多资料&#xff0c;但有价值的很少&#xff0c;或许是太简单&#xff0c;或者是很少人这么用&#xff0c;或者。。。&#xff0c;本文将作者近礼拜摸索到的一点经验分享出来&#xff0c;给自己做个记录。 材料&#…

Linux下基于最新稳定版ESP-IDF5.3.2开发esp32s3入门任务间的通讯-消息队列【入门四】

继续上一篇任务创建 【Linux下基于最新稳定版ESP-IDF5.3.2开发esp32s3入门任务间的通讯-信号量【入门三】-CSDN博客】 今天要实现消息队列进行任务的通讯 一、从上一篇信号量通讯demo拷贝一份重命名&#xff0c;还是之前的两个任务&#xff0c;重命名了。 xTaskCreatePinned…

workman服务端开发模式-应用开发-后端api推送修改二

需要修改两个地方&#xff0c;第一个是总控制里面的续token延时&#xff0c;第二个是操作日志记录 一、总控续token延时方法 在根目录下app文件夹下controller文件夹下Base.php中修改isLoginAuth方法&#xff0c;具体代码如下&#xff1a; <?php /*** 总控制* User: 龙哥…

ReactPress 1.6.0:重塑博客体验,引领内容创新

ReactPress 是一个基于Next.js的博客&CMS系统&#xff0c; Github项目地址&#xff1a;https://github.com/fecommunity/reactpress 欢迎Star。 体验地址&#xff1a;http://blog.gaoredu.com/ 今天&#xff0c;我们自豪地宣布ReactPress 1.6.0版本的正式发布&#xff0c;…

重拾设计模式--外观模式

文章目录 外观模式&#xff08;Facade Pattern&#xff09;概述定义 外观模式UML图作用 外观模式的结构C 代码示例1C代码示例2总结 外观模式&#xff08;Facade Pattern&#xff09;概述 定义 外观模式是一种结构型设计模式&#xff0c;它为子系统中的一组接口提供了一个统一…

接口测试Day03-postman断言关联

postman常用断言 注意&#xff1a;不需要手敲&#xff0c;点击自动生成 断言响应状态码 Status code&#xff1a;Code is 200 //断言响应状态码为 200 pm.test("Status code is 200", function () {pm.response.to.have.status(200); });pm: postman的实例 test() …

2024小迪安全信息收集第七课

目录 ICO图标 Django Flask Tornado 一、#JavaScript-开发框架-Vue&Node.js Vue Node.js 二、#PHP-开发框架-ThinkPHP&Laravel&Yii ThinkPHP Laravel Yii 三、#Java-框架组件-Fastjson&Shiro&Solr&Spring 52类110个主流Java组件和框架介绍…