当前位置: 首页 > news >正文

Spring Boot 缓存实现方案,缓存是提升性能、降低数据库压力的重要手段(单机应用推荐 Spring Cache + Caffeine)

在 Spring Boot 项目中,缓存是提升性能、降低数据库压力的重要手段。下面结合你提供的资金选项查询业务(/capital/options/all接口),对 Spring Boot 项目中使用缓存的方方面面做一个详细总结,涵盖 Spring Cache 抽象、本地缓存 Caffeine、分布式缓存 Redis 以及 MyBatis 自带的二级缓存,并给出最佳实践建议。


一、缓存的使用场景与业务分析

你的业务接口/capital/options/all用于查询资金相关的所有下拉选项,包括资金账户、来源、指标来源等 7 张配置表。这类数据的特点是:

  • 读多写极少:配置数据基本固定,只在后台管理修改时更新。

  • 数据量小:每张表可能只有几十到几百条记录。

  • 响应要求高:前端可能频繁调用该接口。

因此,非常适合引入缓存,将全量查询结果缓存起来,避免每次请求都查询数据库。


二、Spring Boot 中的缓存方案概览

Spring Boot 支持多种缓存方案,主要分为两类:

  1. 基于 Spring 缓存抽象spring-boot-starter-cache

    • 通过注解(@Cacheable@CacheEvict等)声明式地控制缓存。

    • 可插拔的缓存实现:Caffeine(本地)、Redis(分布式)、Ehcache 等。

    • 与业务代码解耦,切换实现无需修改业务逻辑。

  2. MyBatis 自带的二级缓存

    • MyBatis 框架内置的缓存机制,作用于 Mapper 级别。

    • 无需额外依赖,直接在 Mapper 上配置即可。

    • 缓存粒度较细,但无法直接缓存组合后的 DTO。

下面将分别详细说明这两种方案在业务中的应用。


三、方案一:Spring Cache 抽象(推荐)

1. 核心概念

  • @Cacheable:标记在方法上,表示结果可缓存。首次调用后缓存结果,后续直接返回缓存值。

  • @CacheEvict:清除缓存,通常在更新操作后调用,保证数据一致性。

  • @CachePut:每次都会执行方法,并更新缓存(不常用)。

  • CacheManager:缓存管理器,负责创建和管理缓存实例。

2. 集成步骤(以 Caffeine 本地缓存为例)

2.1 添加依赖

xml

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> </dependency>
2.2 启用缓存

在启动类或任意@Configuration类上加@EnableCaching

java

@SpringBootApplication @EnableCaching public class Application { ... }
2.3 配置缓存管理器(可选但推荐)

application.yml中指定缓存类型和基本属性:

yaml

spring: cache: type: caffeine caffeine: spec: maximumSize=500, expireAfterWrite=10m # 最大500条,写入10分钟后过期

或者通过 Java 配置类进行更细粒度的控制。

2.4 在 Service 方法上添加缓存注解

针对你的业务,改造CapitalInfoService

java

@Service public class CapitalInfoService { // 注入各个 Mapper @Cacheable(value = "capitalOptions", key = "'all'") public CapitalAllOptionDTO queryCapitalAllOption() { // 原有的7次查询逻辑 return allOptionDTO; } @CacheEvict(value = "capitalOptions", key = "'all'") public void refreshCapitalOptions() { // 空方法,用于手动清空缓存(例如在更新配置后调用) } // 更新配置的方法示例 @CacheEvict(value = "capitalOptions", key = "'all'") public void updateAccount(CapitalAccount account) { accountMapper.updateById(account); } }
2.5 注意事项
  • 缓存键key = "'all'"使用固定字符串,因为该方法无参且返回全量数据。如果方法有参数,可以动态生成键(如#id)。

  • 序列化:如果将来要切换 Redis,需要让CapitalAllOptionDTO及其包含的实体类实现Serializable

  • 缓存清理:任何对配置表的修改操作(增、删、改)都必须清除缓存,否则用户会看到旧数据。可以在每个更新方法上加@CacheEvict,或统一调用refreshCapitalOptions()

  • 空值缓存:如果数据库为空,默认不会缓存空结果,可能导致缓存穿透。可以用unless = "#result == null"避免。

3. 切换为 Redis(分布式缓存)

如果应用需要多实例部署,只需更换依赖和配置,业务代码无需修改:

  • 移除 Caffeine 依赖,添加spring-boot-starter-data-redis

  • application.yml中配置 Redis 连接和 TTL:

yaml

spring: cache: type: redis redis: time-to-live: 600000 # 10分钟(毫秒) redis: host: localhost port: 6379

Spring Boot 会自动配置RedisCacheManager,之前的@Cacheable注解继续生效。

4. Spring Cache 的优势

  • 注解驱动:简洁明了,与业务代码分离。

  • 灵活:可缓存任意方法的返回值,包括组合 DTO。

  • 可扩展:轻松切换本地/分布式缓存。

  • 支持多种缓存实现:Caffeine、Redis、Ehcache 等。


四、方案二:MyBatis 二级缓存

1. 核心概念

MyBatis 内置了两级缓存:

  • 一级缓存(SqlSession 级别):默认开启,同一会话中重复查询不查库。

  • 二级缓存(Mapper 级别):跨会话共享,需手动开启。

二级缓存的作用域是同一个namespace(即同一个 Mapper),缓存的是 Mapper 中查询方法的返回结果(如List<CapitalAccount>)。

2. 在业务中的应用

如果使用 MyBatis 二级缓存,你需要为 7 个 Mapper 分别开启缓存:

java

@Mapper @CacheNamespace(eviction = FifoCache.class, flushInterval = 60000, size = 512) public interface CapitalAccountMapper extends BaseMapper<CapitalAccount> { }

然后,在 Service 方法中,每次调用accountMapper.selectList(null)时,如果二级缓存中有数据,就不会查询数据库。

3. 优缺点分析

优点

  • 无需额外依赖,MyBatis 自带。

  • 配置简单,只需在 Mapper 上加注解。

  • 自动维护一致性:执行当前 Mapper 的 DML 操作时自动清空该 Mapper 的缓存。

缺点

  • 无法缓存组合后的 DTO,每次调用queryCapitalAllOption()仍需执行 7 次查询(虽然可能命中缓存)。

  • 缓存粒度太细,不能跨 Mapper 共享。

  • 分布式环境下,各实例缓存独立,会导致数据不一致。

  • 需要实体类实现Serializable(如果缓存可读写)。

  • 与 Spring 事务结合时,需要注意缓存清空的时机(必须在事务提交后清空,MyBatis 默认在更新后清空,但如果事务回滚,缓存已被清空,可能造成不一致)。

4. 适合场景

  • 单机应用,对某个 Mapper 的查询有较高重复率。

  • 无需跨表组合缓存的简单场景。

对于你的业务(一次返回 7 张表的组合数据),MyBatis 二级缓存虽然能减少数据库查询,但仍有多次缓存读取和对象组装开销,不如 Spring Cache 直接缓存最终 DTO 高效。


五、两种方案的对比与选择

特性Spring Cache + Caffeine/RedisMyBatis 二级缓存
缓存粒度任意方法返回值(如 DTO)Mapper 级别的单个查询结果
配置复杂度需添加依赖和配置,但注解简单无需额外依赖,仅 Mapper 配置
数据一致性需手动@CacheEvict,可精细控制执行 DML 时自动清空当前 Mapper 缓存
分布式支持支持(切换 Redis)不支持(仅本地)
与业务解耦完全解耦,可作用于 Service 层与 Mapper 耦合,无法控制组合逻辑
适用场景适合组合查询、跨表结果缓存适合单表简单查询的重复缓存

针对你的业务,强烈推荐使用 Spring Cache 抽象 + Caffeine(单机)或+ Redis(分布式)。理由如下:

  • 直接缓存最终的CapitalAllOptionDTO,一次方法调用只需查一次数据库(缓存失效后),性能最佳。

  • 注解使用灵活,未来可无缝切换缓存实现。

  • 代码改动小,只需在 Service 方法上加注解,并在更新操作上加@CacheEvict


六、最佳实践总结(结合你的业务)

1. 实施步骤

  1. 添加依赖spring-boot-starter-cachecaffeine

  2. 启用缓存:启动类加@EnableCaching

  3. 配置缓存application.yml中设置spring.cache.caffeine.spec=maximumSize=500, expireAfterWrite=10m

  4. 注解 Service 方法

    • queryCapitalAllOption()上加@Cacheable(value = "capitalOptions", key = "'all'")

    • 所有更新配置的方法(如updateAccountaddSource等)上加@CacheEvict(value = "capitalOptions", key = "'all'")

  5. 测试验证:首次调用查库,第二次直接返回缓存。

2. 注意事项

  • 缓存过期时间:根据数据更新频率设置合理的expireAfterWrite,例如 10 分钟或 30 分钟。即使有@CacheEvict,过期时间作为兜底,防止缓存永久占用内存。

  • 缓存清理:务必在所有修改操作中清除缓存,否则会出现脏数据。如果修改操作分散在多个 Service,可以统一调用一个刷新方法(@CacheEvict标注的空方法)。

  • 序列化:如果未来可能切换 Redis,提前让 DTO 和实体类实现Serializable

  • 缓存键冲突:如果同一缓存区域有其他方法,使用不同的 key 区分。

  • 监控与调优:可以通过 Actuator 端点查看缓存命中率,调整缓存大小和过期时间。

3. 扩展思考

  • 分布式部署:当应用需要水平扩展时,将缓存实现切换为 Redis,注意配置 Redis 的序列化方式(如 Jackson2JsonRedisSerializer)以便跨语言读取。

  • 缓存穿透/雪崩:配置空值缓存(unless)和随机过期时间防止雪崩。

  • 多级缓存:对于极高并发场景,可考虑 Caffeine 作为一级缓存,Redis 作为二级缓存,但会增加复杂度。


七、总结

Spring Boot 项目中使用缓存,应根据业务场景选择合适的方案:

  • 对于组合查询、跨表结果,首选Spring Cache 抽象,它提供了声明式编程模型,能够灵活地缓存任意方法的返回值,并且支持多种缓存实现,方便未来扩展。

  • 对于简单的单表查询重复缓存,MyBatis 二级缓存也是一个轻量级的选择,但需注意其局限性和分布式问题。

在你的资金选项业务中,采用 Spring Cache + Caffeine 的实践方案,不仅能够显著提升接口性能,还能保持代码简洁、易于维护。缓存是性能优化的利器,但也要注意数据一致性、过期策略和分布式环境下的挑战,合理设计才能发挥最大价值。

http://www.jsqmd.com/news/495892/

相关文章:

  • CYBER-VISION零号协议Keil5项目开发:嵌入式AI集成调试技巧
  • 深入解析BUCK电感工作模式:CCM、DCM与BCM的实战对比
  • 保姆级教程:从零开始搭建华为eNSP模拟器环境(含WinPcap/Wireshark配置)
  • 动态生成网页
  • Cosmos-Reason1-7B实际项目:新能源电池装配线动作合规性分析
  • STM32开发必备:ST-LINK Utility 4.6.0保姆级安装教程(含驱动自动安装)
  • LumiPixel Canvas Quest WebUI插件开发入门:自定义功能扩展
  • 教育场景新利器:Fish-Speech 1.5快速制作教学音频素材
  • 探索电视盒子的无限可能:用Armbian打造专属家庭服务器
  • 老旧设备系统升级指南:OpenCore Legacy Patcher技术解析与实战应用
  • 造相-Z-Image-Turbo生成效果深度评测:不同采样器与参数对比展示
  • STM32F042F6P6+DHT11温湿度检测实战:从硬件选型到串口数据可视化
  • Stable-Diffusion-V1-5 复古未来主义视觉作品:重新想象80年代的科技美学
  • 从Deeplabv1到v3+:演进之路与核心创新解析
  • 春联生成模型-中文-base:如何生成对仗工整、平仄协调的专业春联?
  • 解密Qwen3- Next的Gated DeltaNet:如何用75%混合层实现长文本高效推理
  • Acwing算法基础课实战:KMP字符串匹配算法详解与代码实现
  • Cosmos-Reason1-7B与Dify集成实战:可视化构建AI智能体应用
  • GitHub开源聊天工具Zulip:从团队协作到开源社区的全方位应用
  • GB28181协议下海康NVR通道管理避坑指南:解决503错误的3个关键步骤
  • Java调用Python脚本的终极指南:在若依系统中集成AI识别功能(附dlib安装避坑)
  • 如何用jsDelivr加速你的开源项目?5分钟搞定CDN配置(附常见问题解决方案)
  • LIO-SAM算法实战:从理论到代码实现
  • Realistic Vision V5.1 微信小程序开发:集成AI写真生成功能实战
  • 冷/温/热备份全对比:中小团队如何用最低成本搭建合规容灾系统(附PHPStudy配置示例)
  • 基于NE555与单片机的可调频率脉冲发生器设计与实现
  • llama.cpp最新版Windows编译全记录:从源码下载到模型测试(含w64devkit配置)
  • Z-Image-Turbo_UI界面效果展示:高清AI绘画作品惊艳案例分享
  • 如何利用开源学习资源实现高效复习:中山大学SYSU-Exam项目全攻略
  • 使用FLUX小红书极致真实V2增强网络安全态势可视化