Spring Boot 缓存实现方案,缓存是提升性能、降低数据库压力的重要手段(单机应用推荐 Spring Cache + Caffeine)
使用示例
1、添加缓存依赖
pom.xml
<!-- Spring Cache:Spring 缓存抽象【必须】 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- 【可选】可插拔的缓存实现:Caffeine 实现/缓存管理:高性能本地缓存,支持过期、大小限制。同类的实现/缓存管理有:Caffeine、Redis、Ehcache 等-->
<!-- 【可选】可插拔的缓存实现:Redis 实现/缓存管理:分布式缓存,支持多实例共享 -->
<!-- 【不选】实现/缓存管理:不加具体实现/缓存管理 → 缓存可以工作,使用默认的 ConcurrentMapCacheManager(基于内存,无过期)。 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
</dependencies>
2、配置缓存
src/main/resources/application-dev.yml
src/main/resources/application-prod.yml
spring:
# 缓存配置
cache:
# type 可以是 caffeine,或 redis
type: caffeine
# caffeine 的配置
caffeine:
# maximumSize 最大条目:500条,expireAfterWrite 过期时间:5分钟
spec: maximumSize=500, expireAfterWrite=5m
# redis 的配置
redis:
# 过期时间(毫秒)
time-to-live: 300000
3、开启缓存
src/main/java/com/weiyu/LimsServerApplication.java
package com.weiyu;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync // 开启异步
// @EnableTransactionManagement // 显式开启事务管理(默认开启)
// @EnableScheduling // 开启调度(计划任务),可以在主程序这里开启,也可以配置类中开启,或者这两个地方都开启也不会有冲突
@MapperScan("com.weiyu.mapper") // 统一设置接口扫描,这样就不用每个Mapper文件使用@Mapper了
@EnableCaching // 开启缓存,可以在主程序这里开启,也可以配置类中开启,或者这两个地方都开启也不会有冲突
// @EnableCaching 的核心是通过 @Import(CachingConfigurationSelector.class) 导入缓存相关的配置类。
// Spring 在解析配置时,在处理这类 @Enable* 注解时,会进行去重处理,会自动确保同一个配置类只被加载一次,因此多次标注 @EnableCaching 是安全的,不建议多次标注。
public class LimsServerApplication {
public static void main(String[] args) {
SpringApplication.run(LimsServerApplication.class, args);
}
}
4、使用缓存
src/main/java/com/weiyu/service/CapitalInfoService.java
/**
* 查询资金所有选项
*/
@Cacheable(value = "capitalOptions", key = "'all'")
public CapitalAllOptionDTO queryCapitalAllOption() {
List<CapitalAccount> accounts = accountMapper.selectList(null);
List<CapitalSource> sources = sourceMapper.selectList(null);
List<CapitalIndexSource> indexSources = indexSourceMapper.selectList(null);
List<CapitalIndexType> indexTypes = indexTypeMapper.selectList(null);
List<CapitalBudget> budgets = budgetMapper.selectList(null);
List<CapitalPayType> payTypes = payTypeMapper.selectList(null);
List<CapitalPayMode> payModes = payModeMapper.selectList(null);
CapitalAllOptionDTO allOptionDTO = new CapitalAllOptionDTO();
allOptionDTO.setCapitalAccounts(accounts);
allOptionDTO.setCapitalSources(sources);
allOptionDTO.setCapitalIndexSources(indexSources);
allOptionDTO.setCapitalIndexTypes(indexTypes);
allOptionDTO.setCapitalBudgets(budgets);
allOptionDTO.setCapitalPayTypes(payTypes);
allOptionDTO.setCapitalPayModes(payModes);
return allOptionDTO;
}
4、清空缓存
src/main/java/com/weiyu/service/CapitalInfoService.java
/**
* 清空资金所有选项缓存
*/
@CacheEvict(value = "capitalOptions", allEntries = true)
public void clearCapitalAllOptionCache() {
// 方法体可以为空,仅用于清空缓存
}
详细讲解
在 Spring Boot 项目中,缓存是提升性能、降低数据库压力的重要手段。下面结合你提供的资金选项查询业务(/capital/options/all 接口),对 Spring Boot 项目中使用缓存的方方面面做一个详细总结,涵盖 Spring Cache 抽象、本地缓存 Caffeine、分布式缓存 Redis 以及 MyBatis 自带的二级缓存,并给出最佳实践建议。
一、缓存的使用场景与业务分析
你的业务接口 /capital/options/all 用于查询资金相关的所有下拉选项,包括资金账户、来源、指标来源等 7 张配置表。这类数据的特点是:
-
读多写极少:配置数据基本固定,只在后台管理修改时更新。
-
数据量小:每张表可能只有几十到几百条记录。
-
响应要求高:前端可能频繁调用该接口。
因此,非常适合引入缓存,将全量查询结果缓存起来,避免每次请求都查询数据库。
二、Spring Boot 中的缓存方案概览
Spring Boot 支持多种缓存方案,主要分为两类:
-
基于 Spring 缓存抽象(
spring-boot-starter-cache)-
通过注解(
@Cacheable、@CacheEvict等)声明式地控制缓存。 -
可插拔的缓存实现:Caffeine(本地)、Redis(分布式)、Ehcache 等。
-
与业务代码解耦,切换实现无需修改业务逻辑。
-
-
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/Redis | MyBatis 二级缓存 |
|---|---|---|
| 缓存粒度 | 任意方法返回值(如 DTO) | Mapper 级别的单个查询结果 |
| 配置复杂度 | 需添加依赖和配置,但注解简单 | 无需额外依赖,仅 Mapper 配置 |
| 数据一致性 | 需手动 @CacheEvict,可精细控制 |
执行 DML 时自动清空当前 Mapper 缓存 |
| 分布式支持 | 支持(切换 Redis) | 不支持(仅本地) |
| 与业务解耦 | 完全解耦,可作用于 Service 层 | 与 Mapper 耦合,无法控制组合逻辑 |
| 适用场景 | 适合组合查询、跨表结果缓存 | 适合单表简单查询的重复缓存 |
针对你的业务,强烈推荐使用 Spring Cache 抽象 + Caffeine(单机)或 + Redis(分布式)。理由如下:
-
直接缓存最终的
CapitalAllOptionDTO,一次方法调用只需查一次数据库(缓存失效后),性能最佳。 -
注解使用灵活,未来可无缝切换缓存实现。
-
代码改动小,只需在 Service 方法上加注解,并在更新操作上加
@CacheEvict。
六、最佳实践总结(结合你的业务)
1. 实施步骤
-
添加依赖:
spring-boot-starter-cache和caffeine。 -
启用缓存:启动类加
@EnableCaching。 -
配置缓存:
application.yml中设置spring.cache.caffeine.spec=maximumSize=500, expireAfterWrite=10m。 -
注解 Service 方法:
-
queryCapitalAllOption()上加@Cacheable(value = "capitalOptions", key = "'all'")。 -
所有更新配置的方法(如
updateAccount、addSource等)上加@CacheEvict(value = "capitalOptions", key = "'all'")。
-
-
测试验证:首次调用查库,第二次直接返回缓存。
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 的实践方案,不仅能够显著提升接口性能,还能保持代码简洁、易于维护。缓存是性能优化的利器,但也要注意数据一致性、过期策略和分布式环境下的挑战,合理设计才能发挥最大价值。
八、相关知识
配置缓存,使用 application.yml 配置,同时又使用 Java 配置类,这样可以吗?
可以同时使用,但需要注意优先级和覆盖关系,否则可能导致配置不生效或产生意外行为。
1. 同时使用时会发生什么?
-
Spring Boot 自动配置:当你在
application.yml中配置了spring.cache相关属性(如type、caffeine.spec等),Spring Boot 会根据这些属性自动创建对应的CacheManagerBean。 -
自定义 Java 配置类:如果你在某个
@Configuration类中又手动定义了一个CacheManager的@Bean,那么这个自定义的 Bean 会覆盖自动配置的 CacheManager。此时,application.yml中与缓存相关的配置(如spring.cache.caffeine.spec)将不再生效,因为 Spring Boot 的自动配置类(如CaffeineCacheConfiguration)会在检测到已有CacheManagerBean 时自动退让(@ConditionalOnMissingBean)。
2. 示例说明
application.yml 配置:
yaml
spring:
cache:
type: caffeine
caffeine:
spec: maximumSize=500, expireAfterWrite=10m
Java 配置类:
java
@Configuration
public class CustomCacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(100)
.expireAfterWrite(30, TimeUnit.MINUTES));
return cacheManager;
}
}
结果:实际生效的是 Java 配置类中定义的缓存管理器(最大 100 条,30 分钟过期),application.yml 中的配置被忽略。
3. 为什么建议只使用一种方式?
-
避免混乱:如果两种方式混用,后续维护者可能不清楚最终生效的配置是哪一个,需要检查代码和配置文件才能确定。
-
简化调试:所有缓存配置集中在一处,更容易排查问题。
-
利用自动配置:如果需求简单(如仅设置过期时间和大小),直接用
application.yml配置最简洁,无需编写 Java 代码。
4. 如何结合使用(如果需要)
如果你确实需要既使用 application.yml 的灵活性,又想在 Java 配置中做一些动态处理,可以通过 @ConfigurationProperties 读取配置属性,然后在 Java 配置中应用这些值。例如:
java
@Configuration
public class CustomCacheConfig {
@Bean
@ConfigurationProperties(prefix = "spring.cache.caffeine")
public CaffeineSpec caffeineSpec() {
return CaffeineSpec.parse(""); // 默认空,实际由配置绑定
}
@Bean
public CacheManager cacheManager(CaffeineSpec caffeineSpec) {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.from(caffeineSpec));
return cacheManager;
}
}
这样,application.yml 中的 spring.cache.caffeine.spec 仍然会通过 CaffeineSpec Bean 注入,实现了配置的外部化,同时又可以在 Java 配置中做一些额外定制(如注册多个缓存区域)。
5. 最佳实践建议
-
简单场景:仅使用
application.yml配置,利用 Spring Boot 自动配置,代码最简洁。 -
复杂定制场景:仅使用 Java 配置类,完全接管缓存管理器的创建,并在配置类内部定义所有规则。
-
除非必要,不要混合两种方式定义同一个
CacheManager,以免产生不可预知的结果。
总结
-
可以同时使用,但 Java 配置类中定义的
CacheManagerBean 会覆盖application.yml的自动配置。 -
推荐选择一种方式保持配置一致,避免混淆。
-
若需结合,可通过
@ConfigurationProperties将 yml 属性注入到 Java 配置中使用。
生产环境中,缓存设置多长时间过期比较好?
缓存过期时间(TTL)的设置没有绝对的标准,需要根据业务对数据实时性的要求和数据本身的变更频率来权衡。结合你提到的“资金选项”配置表场景,可以从以下几个角度考虑:
1. 业务一致性要求
-
强一致性:如果配置数据修改后,要求用户立即看到最新值(例如关键业务规则配置),那么缓存时间应尽可能短(如几秒到几分钟),或者干脆不设过期时间,完全依赖更新操作时主动清除缓存(
@CacheEvict)。 -
弱一致性:如果允许一定的延迟(比如几分钟到几十分钟),则可以设置较长的过期时间。
2. 数据变更频率
-
配置表通常变更极低(例如每月甚至每季度才修改一次)。这种情况下,即使缓存设置一天甚至一周过期,也不会造成数据长时间不一致。但需考虑后台管理员修改配置后,期望多久生效。
-
高频率更新的数据(如实时库存),则必须设置极短的过期时间或采用其他同步机制。
3. 推荐策略:主动失效 + 兜底过期
对于你的配置表业务,最佳实践是:
-
主动失效:在所有更新配置的接口(增、删、改)上添加
@CacheEvict,保证修改后缓存立即清除,下次查询即拉取最新数据。 -
兜底过期:同时设置一个合理的过期时间(如 30 分钟、1 小时、甚至 24 小时),作为安全网。这样即使由于某些原因(如程序 bug)忘记调用清除方法,缓存也不会永久占用内存,且数据最终会更新。
4. 常见过期时间参考
-
业务容忍度:如果允许配置更新后最长 10 分钟可见旧数据,则 TTL 可设为 10 分钟。
-
内存与性能平衡:配置表数据量小,即使 TTL 设为一两天也无内存压力。
-
避免缓存雪崩:如果多个缓存键同时过期,可能导致大量请求穿透到数据库。可设置随机偏移量,例如在配置文件中设定一个范围(如 10~15 分钟)让 Spring Cache 自动随机化(需自定义
CacheManager或利用 Caffeine 的expireAfterWrite配合Jitter)。
5. 具体建议值(针对你的业务)
-
初始推荐:设置为 30 分钟。这个时间既能接受后台修改的延迟,又能减少数据库压力。
-
调整依据:上线后观察实际更新频率和用户反馈。如果管理员经常抱怨修改后不能立刻生效,则缩短到 5~10 分钟;如果数据几乎不变,可延长到 2 小时甚至更长。
-
配合主动清除:确保所有写操作都调用
@CacheEvict,这样过期时间仅作为兜底,实际数据一致性由主动清除保证。
6. 实现方式示例(Caffeine)
yaml
spring:
cache:
caffeine:
spec: maximumSize=500, expireAfterWrite=30m # 30分钟过期
或使用 Redis:
yaml
spring:
cache:
redis:
time-to-live: 1800000 # 30分钟(单位毫秒)
总结
没有“最好”的过期时间,只有“最适合”当前业务的值。建议从 30 分钟 开始,结合业务反馈和监控数据(缓存命中率、数据库负载)逐步调整。同时务必实现写操作的缓存清除,这样即使 TTL 较长,数据也能及时更新。
maximumSize 是 Caffeine 缓存中用来控制最大条目数的参数。当缓存中的条目数量超过这个值时,Caffeine 会根据淘汰策略(如 LRU,即最近最少使用)自动移除最久未使用的条目,防止内存无限增长。
1. 如何配置 maximumSize
在你的 application.yml 中,可以在 spring.cache.caffeine.spec 里添加:
yaml
spring:
cache:
caffeine:
spec: maximumSize=500, expireAfterWrite=10m
也可以和其他参数组合,例如 maximumSize=500, expireAfterWrite=30m, recordStats。
2. 生产环境中设置多大合适?
这取决于同一个缓存区域(cache name)下可能存在的键值对数量。在你的业务中:
-
你只对
queryCapitalAllOption()方法使用了@Cacheable(value = "capitalOptions", key = "'all'"),这意味着缓存capitalOptions下只有一个键"all"。 -
因此,理论上
maximumSize=1就足够了。但为了应对未来可能增加的其他缓存方法(例如根据ID查询单个配置),建议预留一些空间。
估算方法:
-
当前:只有1个键,
maximumSize设 10 或 100 都绰绰有余。 -
未来扩展:如果将来你会缓存不同条件的查询(例如
findByType、findById),则需预估这些查询条件有多少种组合。例如按ID查询可能有几百个ID,那么maximumSize应设为略大于总记录数,比如500或1000。 -
数据量参考:配置表通常数据量很小,每个表的记录数可能不超过几百。7张表的总记录数可能在几百到一两千之间。如果每张表都需要独立缓存,且每个表有多个查询键,那么总条目数可能达到几千。
建议值:
-
保守起见:先设置为
500或1000,观察内存占用和缓存命中率,再逐步调整。 -
如果确定只有全量查询这一个键:甚至可以省略
maximumSize,因为只有一个键永远不会触发淘汰。但保留一个较小的值(如10)也没坏处。
3. 如果不设置 maximumSize 会怎样?
如果不设置,Caffeine 缓存默认可以无限增长,直到内存耗尽(OOM)。虽然配置表数据量小,短期内无风险,但出于严谨,生产环境务必设置一个合理的上限,避免因程序 bug 导致意外缓存大量数据(例如误将动态参数作为 key)。
4. 与 Redis 的区别
-
Redis 作为独立缓存服务,没有
maximumSize这样的配置,而是通过内存淘汰策略(如maxmemory-policy)和maxmemory来控制整体内存使用。你可以在 Redis 层面设置最大内存,并指定淘汰策略(如allkeys-lru)。 -
如果使用 Spring Cache + Redis,你需要在 Redis 服务器端配置内存限制,Spring 侧只需设置 TTL。
5. 最终推荐配置(结合你的业务)
yaml
spring:
cache:
caffeine:
spec: maximumSize=500, expireAfterWrite=30m
-
maximumSize=500:足以容纳所有配置表的全量查询结果以及可能新增的少量其他缓存键。 -
expireAfterWrite=30m:配合主动@CacheEvict,作为兜底过期时间。
如果未来切换到 Redis,则移除 caffeine 相关配置,添加 Redis TTL:
yaml
spring:
cache:
redis:
time-to-live: 1800000 # 30分钟
同时 Redis 服务器需配置 maxmemory 和淘汰策略。
总结:maximumSize 是防止缓存无限增长的安全网。根据当前业务(单个键)设置一个适中的值(如 500)即可,后续可根据实际使用情况调整。
一定要在启动类上加@EnableCaching吗?
不,不一定要放在启动类上。@EnableCaching 是一个模块开关,只要它被任何一个带有 @Configuration 的类(包括启动类)扫描到,Spring 就会开启缓存功能。
常见的放置位置:
-
启动类(
@SpringBootApplication本身就是一个组合@Configuration)—— 最常用,一目了然。 -
独立的配置类(例如
@Configuration注解的CacheConfig类)—— 适合模块化设计。
只要保证 Spring 能扫描到即可。通常放在启动类上最简单,因为启动类本身就会被组件扫描。
启动类使用了@EnableCaching,又在配置类上使用了@EnableCaching,这会有问题吗?
不会,完全不会有问题。Spring 在处理这类 @Enable* 注解时,会进行去重处理,即使你多次添加,也只会生效一次,不会导致重复配置或报错。
原因:@EnableCaching 的核心是通过 @Import(CachingConfigurationSelector.class) 导入缓存相关的配置类。Spring 在解析配置时,会自动确保同一个配置类只被加载一次,因此多次标注 @EnableCaching 是安全的。
建议:
虽然无害,但从代码整洁性角度,建议只在一个地方(比如启动类)添加 @EnableCaching,避免冗余和潜在的混淆。
如果不配置缓存管理器,缓存多久会过期?
如果不配置缓存管理器(也就是完全采用 Spring Boot 的默认配置),缓存是永不过期的。
下面分两点解释,再告诉你如何确认当前默认使用的是什么缓存。
1. 为什么默认是“永不过期”?
这取决于 Spring Boot 底层默认给你装配了哪个缓存库。简单来说,Spring 的缓存注解(如 @Cacheable)只是一个“指令”,真正的过期策略由背后的缓存实现决定。
下表是几种常见默认情况的行为:
| 使用场景 | 默认缓存行为 | 关键说明 |
|---|---|---|
| 完全默认 (未配置) | 永不过期 | Spring Boot 默认启用 SimpleCacheConfiguration,它使用 ConcurrentMapCache(基于内存的 ConcurrentHashMap)。这个实现没有自动过期机制,数据会一直存在,直到应用重启或被代码手动清除。 |
| 引入 Caffeine 但未配置过期时间 | 永不过期 | Caffeine 本身功能强大,但如果不通过代码或配置文件明确指定 expireAfterWrite 等策略,它也不会自动过期。 |
| 引入 Redis 但未配置过期时间 | 永不过期 | 如果你引入了 Redis,但没有设置 spring.cache.redis.time-to-live 属性,Redis 中的缓存数据同样不会自动过期。 |
2. 如何确认你当前默认用的什么缓存?
你可以通过下面任一方式确认:
-
查看启动日志:在
application.yml中开启 debug 日志,启动项目后搜索SimpleCacheConfiguration,如果有matched字样,说明你当前就是在用默认的ConcurrentMapCache(永不过期)。yaml
# application.yml debug: true
-
查看自动配置报告:在启动日志中,找到
Positive matches(积极匹配)一节,查找Cache相关的自动配置类,看具体是哪一个被匹配到了。
虽然默认是永不过期,但在实际项目中,建议总是根据业务需求配置一个合理的过期时间,防止内存无限增长或数据长期不更新。你可以通过 application.yml 配置全局过期时间,或者通过自定义 CacheManager 为不同缓存设置不同策略。
SimpleCacheConfiguration:
Did not match:
- @ConditionalOnMissingBean (types: org.springframework.cache.CacheManager; SearchStrategy: all) found beans of type 'org.springframework.cache.CacheManager' cacheManager (OnBeanCondition)
Matched:
- Cache org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration automatic cache type (CacheCondition)
SpringApplicationAdminJmxAutoConfiguration:
Did not match:
- @ConditionalOnProperty (spring.application.admin.enabled=true) did not find property 'enabled' (OnPropertyCondition)
从你提供的日志片段来看,SimpleCacheConfiguration 显示 Did not match,原因是已经存在一个 CacheManager 类型的 Bean(名为 cacheManager)。这意味着 Spring Boot 默认的基于 ConcurrentHashMap 的简单缓存配置没有被采用,当前实际生效的是另一个缓存管理器。
1. 日志含义详解
-
Did not match 部分:
@ConditionalOnMissingBean条件要求当前 Spring 容器中不存在CacheManager类型的 Bean。但日志明确指出found beans of type 'org.springframework.cache.CacheManager' cacheManager,说明已经有一个名为cacheManager的 Bean 存在,所以这个条件失败,SimpleCacheConfiguration不会被加载。 -
Matched 部分:
CacheCondition条件匹配,表明自动缓存类型检查通过,但由于上面 Bean 已存在的条件失败,整体配置类仍被排除。
2. 当前实际使用的缓存管理器是什么?
既然默认的 SimpleCacheConfiguration 没有生效,说明存在其他途径定义的 CacheManager Bean。可能的情况有:
-
你手动定义了一个
CacheManagerBean(例如在@Configuration类中)。 -
其他自动配置类生效,例如:
-
如果你引入了
spring-boot-starter-cache并添加了 Caffeine 依赖,Spring Boot 会自动配置CaffeineCacheManager。 -
如果引入了 Redis 依赖,则会自动配置
RedisCacheManager。 -
如果引入了 Ehcache 依赖,则会自动配置
EhcacheCacheManager。
-
3. 如何确认当前缓存管理器?
你可以在启动日志中搜索 CacheManager 或查看自动配置报告,找到实际匹配的缓存配置类。例如:
-
如果看到
CaffeineCacheConfiguration匹配,则当前使用的是 Caffeine。 -
如果看到
RedisCacheConfiguration匹配,则当前使用的是 Redis。 -
如果看到其他配置,则对应相应实现。
你也可以在代码中注入 CacheManager 并打印其类型来确认:
java
@Autowired
private CacheManager cacheManager;
@PostConstruct
public void checkCacheManager() {
System.out.println("当前缓存管理器: " + cacheManager.getClass().getName());
}
4. 关于缓存过期时间
你之前问到“如果不配置缓存管理器,缓存多久会过期”,现在既然已有自定义或自动配置的 CacheManager,过期时间就取决于该缓存管理器的配置。例如:
-
如果是
CaffeineCacheManager且未设置过期时间,则永不过期(除非通过expireAfterWrite等指定)。 -
如果是
RedisCacheManager且未设置spring.cache.redis.time-to-live,也永不过期。
因此,你需要根据实际使用的缓存管理器,检查其配置来确定缓存过期策略。
5. 建议
-
确认当前缓存管理器类型(通过日志或代码)。
-
根据需求配置过期时间:如果当前没有设置过期时间,建议在配置文件(如
application.yml)中明确指定,例如:yaml
spring: cache: type: caffeine # 或 redis caffeine: spec: maximumSize=500, expireAfterWrite=10m redis: time-to-live: 600000 # 10分钟(毫秒) -
如果不需要复杂缓存,也可以移除其他缓存依赖,让
SimpleCacheConfiguration生效,但注意其默认是永不过期。
总之,你的缓存配置目前并非默认的简单缓存,请进一步检查实际使用的缓存管理器,并确认其过期策略。
我的项目之前从未使用过缓存,现在就是按照你的建议,加依赖、开启缓存、方法上添加缓存注解@Cacheable,就简单操作了这三步
你的情况完全正常!当你引入 spring-boot-starter-cache 和 Caffeine 依赖后,Spring Boot 的自动配置机制检测到类路径中有 Caffeine 库,就会自动创建一个 CaffeineCacheManager 的 Bean,因此默认的 SimpleCacheConfiguration 因 CacheManager 已存在而不再生效。
下面帮你梳理现状和后续操作:
1. 为什么已经有 CacheManager?
-
依赖驱动自动配置:Spring Boot 遵循“约定大于配置”,当你添加了
spring-boot-starter-cache以及具体缓存实现(如 Caffeine),它会自动装配对应的缓存管理器。 -
你的三步操作:
-
加依赖 → 触发了 Caffeine 的自动配置。
-
加
@EnableCaching→ 启用缓存注解支持。 -
加
@Cacheable→ 注解开始工作,实际使用的就是自动创建的CaffeineCacheManager。
-
因此,你的缓存功能已经生效,只是缓存策略是默认的(没有过期时间)。
2. 当前缓存的过期时间是多少?
永不过期。因为自动配置的 CaffeineCacheManager 如果没有通过 spring.cache.caffeine.spec 指定过期策略,默认创建的 Caffeine 缓存不会自动过期。数据会一直存在于内存中,直到应用重启或手动清除。
3. 如何验证当前使用的缓存管理器?
你可以通过一个简单的测试来确认:
java
@SpringBootTest
class YourApplicationTests {
@Autowired
private CacheManager cacheManager;
@Test
void testCacheManager() {
System.out.println("当前缓存管理器: " + cacheManager.getClass().getName());
// 输出类似:com.github.benmanes.caffeine.cache.CaffeineCacheManager
}
}
或者在启动日志中搜索 CaffeineCacheConfiguration,如果看到 Matched 则说明自动配置生效。
4. 如何配置缓存过期时间?
既然缓存管理器已经自动配置好,你只需要在 application.yml 中补充缓存规格即可:
yaml
spring:
cache:
type: caffeine
caffeine:
spec: maximumSize=500, expireAfterWrite=10m # 最大500条,写入10分钟后过期
参数说明:
-
maximumSize:缓存最大条目数。 -
expireAfterWrite:写入后多久过期(支持d(天)、h(时)、m(分)、s(秒))。 -
更多选项如
expireAfterAccess、refreshAfterWrite等可查阅 Caffeine 文档。
如果你想为不同的缓存区域设置不同的策略,可以自定义 CacheManager Bean(参考之前的回答)。
5. 还需要注意什么?
-
数据一致性:如果配置表数据会修改,记得在更新方法上添加
@CacheEvict清除对应缓存,否则用户会一直看到旧数据。 -
内存考虑:即使没有过期时间,只要数据量不大(几百条),内存占用通常可忽略。但为防止潜在内存泄漏,建议配置过期时间。
-
分布式环境:如果以后应用部署多个实例,本地缓存会导致数据不一致,届时可改用 Redis 共享缓存。
总结
你已成功启用缓存,当前使用 Caffeine 实现,默认永不过期。只需在配置文件中补充 spring.cache.caffeine.spec 即可控制过期行为。无需担心日志中的 SimpleCacheConfiguration did not match,那正是自动配置正常工作的表现。
加依赖时,如果只加spring-boot-starter-cache,不加caffeine,缓存可以工作吗?
只添加 spring-boot-starter-cache 依赖,不添加任何具体缓存实现(如 Caffeine、Redis 等),缓存仍然可以工作。Spring Boot 会自动启用一个默认的缓存管理器,即 ConcurrentMapCacheManager(基于内存的 ConcurrentHashMap)。
1. 工作原理
-
依赖分析:
spring-boot-starter-cache本身只是缓存抽象层的起步依赖,它不包含任何具体的缓存实现库。 -
自动配置:Spring Boot 检测到类路径中没有找到任何支持的专业缓存(如 Caffeine、Redis、Ehcache 等),就会自动回退到
SimpleCacheConfiguration,该配置会创建一个ConcurrentMapCacheManager的 Bean。 -
ConcurrentMapCacheManager:使用ConcurrentHashMap作为缓存存储,数据保存在当前应用的堆内存中。
2. 这种缓存的特点
-
✅ 优点:
-
零配置,开箱即用。
-
无需引入额外依赖,适合快速测试或极简单应用。
-
-
❌ 缺点:
-
无过期策略:缓存数据永不过期,除非应用重启或手动调用
cache.evict()清除。 -
内存管理:所有缓存数据都存储在 JVM 堆内存中,如果数据量过大,可能导致 GC 压力甚至内存溢出。
-
无持久化:缓存随应用重启而清空。
-
无分布式支持:仅适用于单机应用,多实例部署时各实例缓存独立,数据不一致。
-
3. 如何验证当前使用的缓存管理器?
在你的 Spring Boot 应用中,可以通过注入 CacheManager 并打印其类型来确认:
java
@Autowired
private CacheManager cacheManager;
@PostConstruct
public void check() {
System.out.println("当前缓存管理器: " + cacheManager.getClass().getName());
}
输出类似于:org.springframework.cache.concurrent.ConcurrentMapCacheManager
4. 生产环境建议
虽然默认的 ConcurrentMapCacheManager 可以工作,但对于生产环境(尤其是有缓存过期、容量控制等需求的场景),强烈建议引入一个专业的缓存实现,例如:
-
Caffeine(高性能本地缓存,支持过期、大小限制)
-
Redis(分布式缓存,支持多实例共享)
只需在 pom.xml 中添加对应依赖,Spring Boot 会自动切换缓存管理器,无需修改业务代码。
5. 总结
-
只加
spring-boot-starter-cache,不加具体实现 → 缓存可以工作,使用ConcurrentMapCacheManager(基于内存,无过期)。 -
这种配置适合学习、测试或极小型单机应用。
-
实际项目中建议搭配 Caffeine 或 Redis,以获得更完善的缓存控制。
如果你已经按之前的指导添加了 Caffeine 依赖,那么现在用的就是 CaffeineCacheManager,无需担心。
那不加spring-boot-starter-cache,缓存能工作吗?
如果不加 spring-boot-starter-cache,通常情况下你无法使用 Spring 的声明式缓存(即 @Cacheable、@CacheEvict 等注解),原因如下:
1. Spring 缓存抽象需要该 starter
-
spring-boot-starter-cache是一个起步依赖,它引入了 Spring 缓存抽象的核心模块(spring-context-support)以及自动配置所需的基础设施。 -
没有这个依赖,Spring Boot 就不会自动配置
CacheManager,也不会解析@Cacheable等注解——这些注解会被忽略,方法每次都会执行,相当于没有缓存。 -
当然,你可以手动添加
spring-context-support并自己配置CacheManager和切面,但这远不如用 starter 方便,且不是 Spring Boot 的推荐做法。
2. 有没有其他缓存方式不依赖这个 starter?
有,例如:
-
MyBatis 二级缓存:它是在 MyBatis 框架层面实现的,与 Spring 缓存抽象无关。你可以在 Mapper 上配置
<cache/>并依赖 MyBatis 自带的缓存机制(或集成第三方如 Ehcache)。这种方式不需要spring-boot-starter-cache,但它的缓存作用域、注解都与 Spring 的@Cacheable不同,且与 Spring 事务等整合时可能需要注意。 -
手动编码缓存:例如自己用
ConcurrentHashMap写一个缓存工具类,但这样就失去了声明式缓存的便利性,且需要自行处理并发、过期等问题。
3. 结合你的上下文
你之前已经按照建议添加了 spring-boot-starter-cache 和 Caffeine,并且通过日志确认了 CaffeineCacheManager 已自动配置。因此你的缓存功能目前是正常工作的,无需移除任何依赖。如果你因为某种原因考虑移除 spring-boot-starter-cache,那么你之前添加的 @Cacheable 注解将不再生效,每次请求都会重新查询数据库。
总结
-
不加
spring-boot-starter-cache→ Spring 声明式缓存不工作(除非手动复杂配置)。 -
MyBatis 二级缓存 → 可以独立工作,但属于另一套机制。
-
当前项目 → 已经正确配置,保持现有依赖即可。
MyBatis又有什么缓存?
MyBatis 自己也内置了一套强大的两级缓存机制。它和我们之前讨论的 Spring 缓存(@Cacheable)是两套完全独立的体系,可以共存,但作用域和管理方式不同。
MyBatis 的缓存分为一级缓存(本地缓存)和二级缓存(全局缓存)。我把它们的核心区别整理成了一个表格,这样看更清楚:
| 特性 | 一级缓存 (Local Cache / SqlSession Level) | 二级缓存 (Global Cache / Mapper Level) |
|---|---|---|
| 开启状态 | 默认开启,无法关闭。 | 默认关闭,需要手动配置开启。 |
| 作用范围 | 仅限于同一个 SqlSession 内。 |
同一个 namespace (即同一个 Mapper) 下的所有 SqlSession 共享。 |
| 核心目的 | 避免在同一个会话中重复查询同一数据,解决循环引用问题。 | 在多个会话或请求之间共享数据,提升整体查询效率。 |
| 生命周期 | 随着 SqlSession 的结束(关闭或清空)而消失。 |
与应用的生命周期相关,由 Cache 对象管理,可配置过期策略。 |
| 失效时机 | 执行该 SqlSession 的 insert、update、delete 操作并提交后,会被清空。 |
执行当前 namespace 下的 insert、update、delete 操作并提交后,会被清空。 |
🗺️ 一图胜千言:缓存查询流程
它们的查询顺序是这样的:
-
首先,会根据查询条件尝试从二级缓存中获取结果。
-
如果二级缓存中没有,则会去当前
SqlSession的一级缓存中查找。 -
如果一级缓存中也没有,最后才会执行 SQL 去数据库查询。
-
查询到结果后,会按相反的顺序依次存入一级缓存和二级缓存中。
🔍 深入理解一级缓存
-
它是隐形的:因为默认开启,你可能一直在使用它而没有察觉。例如,在同一个方法中两次连续查询同一个
CapitalAccount,第二次就会直接从缓存中获取,不会查询数据库。 -
与 Spring 事务的关系:在整合 Spring 后,如果没有事务,每次 Mapper 操作都会开启并关闭一个新的
SqlSession,导致一级缓存失效。只有在开启了 Spring 事务(如添加@Transactional)的情况下,多个 Mapper 操作才会使用同一个SqlSession,一级缓存才能真正生效。
⚙️ 如何开启和配置二级缓存
如果你想使用二级缓存,可以按以下步骤操作:
-
全局开关:确认 MyBatis 全局配置文件中的
cacheEnabled参数为true(这也是默认值)。xml
<settings> <setting name="cacheEnabled" value="true"/> </settings> -
Mapper 级别开启:在你需要缓存的 Mapper XML 文件(如
CapitalAccountMapper.xml)中添加<cache/>标签,或是在 Mapper 接口上添加@CacheNamespace注解。xml
<!-- 在 CapitalAccountMapper.xml 中 --> <mapper namespace="com.weiyu.mapper.CapitalAccountMapper"> <!-- 开启二级缓存,并可配置相关属性 --> <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/> <!-- ... 其他查询语句 ... --> </mapper>-
eviction:缓存回收策略,如LRU(最近最少使用,默认)、FIFO(先进先出)。 -
flushInterval:刷新间隔(毫秒),即缓存自动过期时间,不设置则只会在执行 DML 时刷新。 -
size:最多缓存多少个对象引用。 -
readOnly:是否为只读缓存。true能提供性能优势,但返回的是对象实例,不可修改;false会返回对象的拷贝,更安全,但有一定性能开销。
-
⚠️ 使用注意
-
数据一致性:无论是几级缓存,MyBatis 的缓存都是本地缓存。如果你的应用部署了多个实例(集群),一个实例上的数据更新只会清空它自己的缓存,其他实例的缓存无法感知,从而产生脏数据。对于配置表这类场景,如果你的应用是单节点部署,使用 MyBatis 二级缓存是可行的;如果是多节点部署,则需要非常谨慎,或者考虑使用 Redis 等分布式缓存方案。
-
DTO 序列化:如果二级缓存配置为
readOnly=false(读写缓存),则缓存的对象(如你的CapitalAccount)需要实现Serializable接口,因为 MyBatis 会通过序列化来返回对象的拷贝。
结合你之前的项目情况,你已经在使用 Spring 的 @Cacheable 了。我的建议是保持现状,继续使用 Spring 的缓存抽象。因为它的注解使用起来更灵活,而且可以无缝切换 Caffeine(本地)和 Redis(分布式),将来应对多节点部署时也更方便。MyBatis 的二级缓存可以作为了解,但在实际项目中,Spring Cache 已经是更为主流和通用的选择。
也就是说不加spring-boot-starter-cache,也可以使用Mybatis来实现缓存,是吗?
是的,完全可以!即使不加 spring-boot-starter-cache,你依然可以使用 MyBatis 自带的二级缓存来实现缓存效果。MyBatis 的缓存机制是它内置的功能,与 Spring 的缓存抽象是两个独立的体系,它们可以共存,也可以单独使用。
1. 如何在没有 spring-boot-starter-cache 的情况下使用 MyBatis 缓存?
你的项目目前已经整合了 MyBatis-Plus(它基于 MyBatis),因此 MyBatis 本身的功能都是可用的。你只需要在 Mapper 层开启二级缓存即可。
配置步骤(无需额外依赖):
-
确保 MyBatis 全局配置中二级缓存已启用(默认就是 true,一般无需修改)。
-
在具体的 Mapper 接口或 XML 中添加缓存配置。
方式一:使用注解(在 Mapper 接口上)
java
@Mapper @CacheNamespace(eviction = FifoCache.class, flushInterval = 60000, size = 512) public interface CapitalAccountMapper extends BaseMapper<CapitalAccount> { }方式二:使用 XML(在 CapitalAccountMapper.xml 中)
xml
<mapper namespace="com.weiyu.mapper.CapitalAccountMapper"> <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/> <!-- 其他语句 --> </mapper> -
让实体类实现 Serializable(如果二级缓存配置为可读写):
java
public class CapitalAccount implements Serializable { private static final long serialVersionUID = 1L; // ... }
之后,MyBatis 就会自动对当前 Mapper 的查询结果进行缓存,执行插入/更新/删除操作时自动清空缓存。
2. MyBatis 缓存 vs Spring 缓存:如何选择?
虽然 MyBatis 缓存可以独立工作,但结合你的具体场景(一个 Service 方法中查询了 7 个不同的 Mapper),MyBatis 二级缓存存在一些局限性:
| 对比维度 | MyBatis 二级缓存 | Spring 缓存抽象(@Cacheable) |
|---|---|---|
| 作用范围 | 每个 Mapper 独立缓存,不同 Mapper 的缓存互不影响。 | 可以跨 Mapper、跨 Service 缓存任意方法的返回值。 |
| 缓存粒度 | 针对 Mapper 中的单个查询方法,无法缓存组合后的 DTO。 | 可以缓存组合后的 CapitalAllOptionDTO,避免多次查库。 |
| 配置复杂度 | 简单,仅需在 Mapper 上添加注解。 | 需要添加依赖和配置,但注解使用更灵活。 |
| 数据一致性 | 执行当前 Mapper 的 DML 时自动清空该 Mapper 的缓存。 | 需要手动通过 @CacheEvict 清除缓存,但可以精细控制。 |
| 分布式支持 | 仅限单机,多实例会出现数据不一致。 | 可通过切换 Redis 等实现分布式缓存。 |
| 与业务逻辑耦合 | 与 Mapper 紧耦合,无法在 Service 层控制。 | 可在 Service 层灵活控制,与业务代码解耦。 |
针对你的 queryCapitalAllOption() 方法,它查询了 7 个不同的表,最终返回一个组合的 DTO。如果使用 MyBatis 二级缓存,你需要为 7 个 Mapper 分别开启缓存,但无法直接缓存最终的 DTO,每次调用该方法仍会执行 7 次查询(虽然可能命中各自的缓存,但仍有多次网络交互和对象组装)。而 Spring 缓存可以直接缓存整个 DTO,一次方法调用只查一次数据库(缓存失效后),性能更优。
3. 如果坚持使用 MyBatis 缓存,你当前代码会怎样?
假设你只为 7 个 Mapper 分别开启了二级缓存,那么:
-
第一次调用
queryCapitalAllOption()时,会执行 7 次 SQL,结果分别存入各自的二级缓存。 -
第二次调用时,每个
selectList(null)都会命中二级缓存,直接从缓存中取出数据,避免了 7 次数据库查询。 -
但注意:这 7 次查询仍然是分别进行的,只是从缓存读取,而不是一次从数据库读取全部。对于内存来说,没有重复查询数据库,但仍有 7 次缓存读取和 DTO 组装的开销。
-
如果某个配置表更新了,只有该 Mapper 的缓存会被清空,其他表的缓存仍然有效,数据可以部分一致。
这种方案也是可行的,但需要你为每个 Mapper 都配置缓存,并确保实体类可序列化。
4. 分布式环境下的问题
如果你的应用将来需要部署多个实例,MyBatis 二级缓存(本地缓存)会导致各实例缓存不一致。例如,实例 A 修改了配置表,清空了自己的缓存,但实例 B 的缓存仍然是旧的,用户请求到实例 B 时就会拿到错误数据。而 Spring 缓存可以轻松切换为 Redis 实现分布式缓存,避免此问题。
5. 结论
-
可以:不加
spring-boot-starter-cache,只用 MyBatis 二级缓存,确实能实现缓存效果。 -
但结合你的场景,使用 Spring 缓存抽象 + Caffeine 的方案更合适,因为它能直接缓存最终的 DTO,代码侵入小,且未来可扩展为分布式缓存。如果你已经按之前的指导添加了相关依赖和注解,建议继续使用 Spring 缓存。
-
如果你仍想尝试 MyBatis 二级缓存,也完全没问题,只需要在 Mapper 上添加
@CacheNamespace注解,并确保实体类实现Serializable即可(同时移除 Service 方法上的@Cacheable等 Spring 缓存注解)。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)