SpringBoot缓存实践

embedded/2025/3/1 15:10:35/

文章目录

    • 一、引言
    • 二、Spring Cache 抽象
      • (一)核心概念与原理
      • (二)优势与局限性
    • 三、集成常用缓存
      • (一)集成 Redis 缓存
        • 1. 集成步骤
        • 2. 踩坑记录与心得体会
      • (二)集成 Ehcache 缓存
        • 1. 集成步骤
        • 2. 踩坑记录与心得体会
    • 四、缓存注解的使用
      • (一)`@Cacheable`
      • (二)`@CachePut`
      • (三)`@CacheEvict`
      • (四)`@Caching`
      • (五)踩坑记录与心得体会
    • 五、总结

一、引言

在当今的软件开发领域,性能优化始终是开发者们关注的核心问题之一。而缓存机制作为一种高效的性能优化手段,能够显著减少数据的重复计算和数据库的频繁访问,从而提升系统的响应速度和整体性能。Spring Boot 作为一款广泛应用的 Java 开发框架,提供了强大且灵活的缓存机制,本文将深入探讨 Spring Boot 缓存机制,包括 Spring Cache 抽象、集成常用缓存(Redis、Ehcache)以及缓存注解的使用,并分享在实践过程中的踩坑记录和心得体会。

二、Spring Cache 抽象

(一)核心概念与原理

Spring Cache 是 Spring 框架为不同的缓存实现提供的统一抽象层。它的核心思想是将缓存操作从业务逻辑中分离出来,通过注解的方式对方法进行标记,从而实现缓存的自动管理。这种设计模式使得开发者可以在不改变业务代码的情况下,轻松切换不同的缓存实现,提高了代码的可维护性和可扩展性。

Spring Cache 主要涉及以下几个核心接口和注解:

  1. CacheManager:作为缓存管理器,负责管理多个缓存实例。它是 Spring Cache 与具体缓存实现之间的桥梁,通过 CacheManager 可以获取和操作不同的缓存
  2. Cache缓存接口,定义了缓存的基本操作,如 getputevict 等。不同的缓存实现(如 Redis、Ehcache)会实现该接口,提供具体的缓存操作逻辑。
  3. @Cacheable:标记方法的返回值应该被缓存。当调用被 @Cacheable 注解标记的方法时,Spring Cache 会首先检查缓存中是否存在该方法的结果。如果存在,则直接从缓存中获取;否则,执行方法并将结果存入缓存
  4. @CachePut:标记方法的返回值应该被更新到缓存中。无论缓存中是否存在该方法的结果,都会执行方法并将结果存入缓存,常用于更新缓存数据的场景。
  5. @CacheEvict:标记方法应该从缓存中移除指定的键。当调用被 @CacheEvict 注解标记的方法时,Spring Cache 会根据指定的键从缓存中移除相应的数据,常用于删除缓存数据的场景。
  6. @Caching:组合多个缓存注解。当一个方法需要同时使用多个缓存注解时,可以使用 @Caching 注解将它们组合在一起。

(二)优势与局限性

Spring Cache 抽象的优势在于它提供了统一的 API,使得开发者可以方便地使用不同的缓存实现,而无需关心具体的缓存操作细节。同时,通过注解的方式进行缓存管理,减少了代码的侵入性,提高了代码的可读性和可维护性。

然而,Spring Cache 抽象也存在一定的局限性。例如,它的缓存注解功能相对简单,对于一些复杂的缓存策略和业务需求,可能无法满足。此外,Spring Cache 抽象是基于方法级别的缓存,对于一些需要细粒度缓存控制的场景,可能不够灵活。

三、集成常用缓存

(一)集成 Redis 缓存

1. 集成步骤

Redis 是一个高性能的键值对存储数据库,常用于分布式缓存。在 Spring Boot 中集成 Redis 缓存,需要完成以下几个步骤:

  • 添加依赖:在 pom.xml 中添加 Spring Boot Redis 依赖。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 配置 Redis:在 application.propertiesapplication.yml 中配置 Redis 连接信息。
spring.redis.host=localhost
spring.redis.port=6379
  • 启用缓存:在主应用类上添加 @EnableCaching 注解。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;@SpringBootApplication
@EnableCaching
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}
2. 踩坑记录与心得体会
  • 序列化问题:在使用 Redis 缓存时,需要注意对象的序列化问题。默认情况下,Spring Boot 使用 JdkSerializationRedisSerializer 进行对象的序列化,这会导致存储在 Redis 中的数据以二进制形式存储,不利于调试和监控。可以使用 Jackson2JsonRedisSerializerGenericJackson2JsonRedisSerializer 将对象序列化为 JSON 格式,提高数据的可读性。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new GenericJackson2JsonRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());return template;}
}
  • 缓存穿透问题缓存穿透是指查询一个不存在的数据,由于缓存中没有该数据,会直接访问数据库,导致数据库压力增大。可以通过在缓存中设置空值或使用布隆过滤器来解决缓存穿透问题。

(二)集成 Ehcache 缓存

1. 集成步骤

Ehcache 是一个开源的、基于 Java 的缓存框架,适合单机应用。在 Spring Boot 中集成 Ehcache 缓存,需要完成以下几个步骤:

  • 添加依赖:在 pom.xml 中添加 Spring Boot Ehcache 依赖。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache</artifactId>
</dependency>
  • 配置 Ehcache:在 src/main/resources 目录下创建 ehcache.xml 文件。
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"updateCheck="false"><defaultCachemaxElementsInMemory="10000"eternal="false"timeToIdleSeconds="120"timeToLiveSeconds="120"overflowToDisk="false"diskPersistent="false"diskExpiryThreadIntervalSeconds="120"/><cache name="myCache"maxElementsInMemory="1000"eternal="false"timeToIdleSeconds="300"timeToLiveSeconds="600"overflowToDisk="false"/>
</ehcache>
  • 启用缓存:同样在主应用类上添加 @EnableCaching 注解。
2. 踩坑记录与心得体会
  • 缓存配置问题:在配置 Ehcache 时,需要注意各个参数的含义和使用场景。例如,maxElementsInMemory 表示缓存中允许存储的最大元素数量,timeToIdleSeconds 表示元素在缓存中闲置的最大时间,timeToLiveSeconds 表示元素在缓存中存活的最大时间。合理配置这些参数可以提高缓存的命中率和性能。
  • 缓存一致性问题:由于 Ehcache 是单机缓存,当多个实例同时访问和修改缓存时,可能会出现缓存一致性问题。可以通过分布式锁或消息队列等方式来解决缓存一致性问题。

四、缓存注解的使用

(一)@Cacheable

@Cacheable 注解用于标记方法的返回值应该被缓存。当调用被 @Cacheable 注解标记的方法时,Spring Cache 会首先检查缓存中是否存在该方法的结果。如果存在,则直接从缓存中获取;否则,执行方法并将结果存入缓存

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;@Service
public class UserService {@Cacheable("users")public User getUserById(Long id) {// 模拟从数据库中查询用户信息System.out.println("Fetching user from database: " + id);return new User(id, "John Doe");}
}

(二)@CachePut

@CachePut 注解用于更新缓存中的数据。无论缓存中是否存在该方法的结果,都会执行方法并将结果存入缓存

import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;@Service
public class UserService {@CachePut("users")public User updateUser(User user) {// 模拟更新数据库中的用户信息System.out.println("Updating user in database: " + user.getId());return user;}
}

(三)@CacheEvict

@CacheEvict 注解用于从缓存中移除指定的键。当调用被 @CacheEvict 注解标记的方法时,Spring Cache 会根据指定的键从缓存中移除相应的数据。

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;@Service
public class UserService {@CacheEvict("users")public void deleteUser(Long id) {// 模拟从数据库中删除用户信息System.out.println("Deleting user from database: " + id);}
}

(四)@Caching

@Caching 注解用于组合多个缓存注解。当一个方法需要同时使用多个缓存注解时,可以使用 @Caching 注解将它们组合在一起。

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;@Service
public class UserService {@Caching(put = {@CachePut(value = "users", key = "#user.id"),@CachePut(value = "userNames", key = "#user.name")}, evict = {@CacheEvict(value = "allUsers", allEntries = true)})public User updateUser(User user) {// 模拟更新数据库中的用户信息System.out.println("Updating user in database: " + user.getId());return user;}
}

(五)踩坑记录与心得体会

  • 缓存键的生成:在使用缓存注解时,需要注意缓存键的生成规则。默认情况下,Spring Cache 会根据方法的参数和方法名生成缓存键,但在某些情况下,可能需要自定义缓存键的生成策略。可以通过 key 属性来指定缓存键,也可以实现 KeyGenerator 接口来自定义缓存键的生成逻辑。
  • 缓存注解的使用顺序:当一个方法同时使用多个缓存注解时,需要注意注解的使用顺序。例如,@CacheEvict 注解通常应该在 @CachePut 注解之前使用,以确保先删除旧的缓存数据,再更新新的缓存数据。

五、总结

Spring Boot 缓存机制为开发者提供了一种简单、高效的方式来实现缓存管理。通过 Spring Cache 抽象,开发者可以方便地集成不同的缓存实现,如 Redis 和 Ehcache,并使用缓存注解来管理缓存。在实际应用中,需要注意缓存的序列化、缓存穿透、缓存一致性等问题,合理配置缓存参数,选择合适的缓存实现和缓存注解,以提高系统的性能和稳定性。


http://www.ppmy.cn/embedded/169081.html

相关文章

fastapi + 异步 sqlalchemy 连接 mysql 断开 2003 问题

资料 1.Fastapi 项目第二天首次访问时数据库连接报错问题Cant connect to MySQL server - 上海-悠悠 - 博客园2.Peewee_同步/异步/断线重连/连接池 - Alex-GCX - 博客园 3.【Python】SQLAlchemy长时间未请求&#xff0c;数据库连接断开的原因、解决方案_sqlalchemy session长…

前端八股——JS+ES6

前端八股&#xff1a;JSES6 说明&#xff1a;个人总结&#xff0c;用于个人复习回顾&#xff0c;将持续改正创作&#xff0c;已在语雀公开&#xff0c;欢迎评论改正。

matlab图论分析之网络构建

在网络构建中&#xff0c;二值化和加权网络的处理是两个关键步骤&#xff1a; 二值化&#xff1a;是将加权网络转换为二值网络&#xff0c;也就是只有0或1&#xff0c;同时保留网络的关键拓扑特性。通常设定一个阈值也即是网络密度&#xff0c;保留权重高于阈值的边&#xff0…

【http://noi.openjudge.cn/】4.3算法之图论——1538:Gopher II

[【http://noi.openjudge.cn/】4.3算法之图论——1538:Gopher II] 题目 查看提交统计提问 总时间限制: 2000ms 内存限制: 65536kB 描述 The gopher family, having averted the canine threat, must face a new predator. The are n gophers and m gopher holes, each at di…

2步本地安装部署国产之光大模型DeepSeek,附Mac安装教程和安装包!

轻松两步本地运行国产大模型DeepSeek&#xff0c;附Windows与Mac教程及安装包&#xff01; 在短短一夜之间&#xff0c;DeepSeek-R1&#xff0c;中国的AI大模型&#xff0c;以惊人的速度崛起&#xff0c;引发了全球科技界的广泛关注。英伟达AI科学家Jim Fan也对此表示惊讶&…

C++番外篇——红黑树模拟实现set与map

问题探究 我们知道&#xff1a;set是K模型&#xff0c;KeyValue&#xff0c;所以如果用红黑树实现set&#xff0c;那么红黑树的每个节点直接存储一个值即可&#xff1a; struct RBTreeNode_set {RBTreeNode_set* _left;//节点的左孩子RBTreeNode_set* _right;//节点的右孩子R…

Java中使用FFmpeg拉取RTSP流

在Java中使用FFmpeg拉取RTSP流并推送到另一个目标地址是一个相对复杂的任务&#xff0c;因为Java本身并没有直接处理视频流的功能。但是&#xff0c;我们可以借助FFmpeg命令行工具来实现这个功能。FFmpeg是一个非常强大的多媒体处理工具&#xff0c;能够处理音频、视频以及其他…

AI关于SHAP分析与列线图(算法)解释线性模型矛盾之处的解释

AI关于SHAP分析与列线图&#xff08;算法&#xff09;解释线性模型矛盾之处的解释 两种解释方法在个案的局部解释方面&#xff0c;有矛盾之处&#xff0c;其背后的原理已经超出了我的知识范畴&#xff0c;以下是询问AI的几个问题&#xff0c;希望能从中梳理出一个合理的解释。…