Spring Boot 3.3.6升级后@Cacheable方法在CI环境缓存失效
Spring Boot 3.3.6升级后@Cacheable测试本地正常但Concourse CI失败
问题现象
本地运行缓存测试时,productBundleService.getAllDistinctAltKeyTypesFromProductBundleAltId()方法会正确触发缓存,3次调用仅查询1次数据库,测试断言通过;但在Concourse CI环境中测试失败,报错显示缓存完全未生效:
org.opentest4j.AssertionFailedError: expected: <3> but was: <0> at com.hmhco.plugins.core.product.ProductBundleAltIdTypesTest.testGetProductAltKeyTypesCached(ProductBundleAltIdTypesTest.java:52)
CI环境中缓存命中次数为0,每次调用都直接查询数据库。
相关代码
测试类
package com.hmhco.plugins.core.product; import com.hmhco.BaseIntegrationTest; import com.hmhco.plugins.core.product.model.ProductBundleAltId; import com.hmhco.plugins.core.product.repo.ProductBundleAltIdRepository; import com.hmhco.plugins.core.product.service.ProductBundleService; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.SpyBean; import java.util.List; import static com.hmhco.plugins.utils.GeneralConstants.DBID; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @SpringBootTest(properties = { "spring.cache.type=caffeine", "spring.cache.jcache.provider=org.ehcache.jsr107.EhcacheCachingProvider", "spring.cache.cache-names=product-bundle-alt-key-types", "spring.cache.caffeine.spec=maximumSize=1000,expireAfterAccess=10s", "spring.profiles.active=test"}) class ProductBundleAltIdTypesTest extends BaseIntegrationTest { @Autowired ProductBundleService productBundleService; @SpyBean ProductBundleAltIdRepository productBundleAltIdRepository; @BeforeEach public void setUp() { productBundleAltIdRepository.save(ProductBundleAltId.builder().altKeyType(DBID).altKey("12").build()); } @AfterEach public void tearDown() { productBundleAltIdRepository.deleteAll(); } @Test void testGetProductAltKeyTypesCached() { int counter = 0; for (int i = 0; i < 3; i++) { if (productBundleService.getAllDistinctAltKeyTypesFromProductBundleAltId().equals(List.of(DBID))) counter++; } Assertions.assertEquals(3, counter); verify(productBundleAltIdRepository, times(1)).getAllDistinctAltKeyIds(); } }
Service层缓存方法
@Cacheable(value = "product-bundle-alt-key-types") @Transactional(readOnly = true) public List<String> getAllDistinctAltKeyTypesFromProductBundleAltId() { return productBundleAltIdRepository.getAllDistinctAltKeyIds(); }
Repository查询方法
@Query("SELECT DISTINCT altKeyType FROM ProductBundleAltId") List<String> getAllDistinctAltKeyIds();
已知信息
- 存在结构完全一致的测试类(仅实体不同),在本地和CI环境均能正常通过
- 问题始于Spring Boot升级到3.3.6版本(为修复Snyk漏洞),升级时同步更新了适配依赖
- 已尝试调整缓存过期时间、添加
@DirtiesContext注解重载上下文,均未解决问题
排查方向与解决方案
1. 验证CI环境实际生效的缓存配置
在测试中添加日志输出当前缓存配置,确认CI环境是否正确加载了测试类指定的缓存参数:
@Autowired private CacheManager cacheManager; @Test void testGetProductAltKeyTypesCached() { // 打印缓存类型与可用缓存名称 System.out.println("Cache Manager Type: " + cacheManager.getClass().getName()); System.out.println("Active Caches: " + cacheManager.getCacheNames()); // 原测试逻辑... }
查看CI日志,确认缓存类型为Caffeine,且包含product-bundle-alt-key-types缓存。若配置不匹配,需检查CI环境是否有外部配置文件、环境变量覆盖了测试类的properties。
2. 调整事务与缓存的执行顺序
@Transactional与@Cacheable的叠加可能因Spring Boot 3.3.x的代理逻辑变化导致缓存失效。可尝试:
- 将
@Cacheable移至非事务方法,通过该方法调用事务方法:
@Cacheable(value = "product-bundle-alt-key-types") public List<String> getCachedDistinctAltKeyTypes() { return getAllDistinctAltKeyTypesFromProductBundleAltId(); } @Transactional(readOnly = true) public List<String> getAllDistinctAltKeyTypesFromProductBundleAltId() { return productBundleAltIdRepository.getAllDistinctAltKeyIds(); }
- 调整事务传播属性为
SUPPORTS,避免事务强制包裹缓存逻辑:
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
3. 确认Caffeine依赖版本适配
Spring Boot 3.3.6默认适配Caffeine 3.1.8,若CI环境依赖拉取异常,可能导致缓存实例创建失败。在pom.xml中显式指定版本:
<dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>3.1.8</version> </dependency>
4. 强制使用CGLIB代理
Spring Boot默认对无接口的类使用CGLIB代理,若CI环境类加载逻辑异常,可强制指定代理模式:
在测试类的@SpringBootTest properties中添加:
spring.aop.proxy-target-class=true
5. 优化测试逻辑,直接验证缓存状态
修改测试用例,跳过循环判断,直接检查缓存是否写入:
@Test void testGetProductAltKeyTypesCached() { // 第一次调用,触发DB查询 List<String> firstResult = productBundleService.getAllDistinctAltKeyTypesFromProductBundleAltId(); Assertions.assertEquals(List.of(DBID), firstResult); // 验证缓存存在且值正确 Cache cache = cacheManager.getCache("product-bundle-alt-key-types"); Assertions.assertNotNull(cache); Object cachedValue = cache.get(SimpleKey.EMPTY).get(); Assertions.assertEquals(List.of(DBID), cachedValue); // 第二次调用,验证不触发DB查询 List<String> secondResult = productBundleService.getAllDistinctAltKeyTypesFromProductBundleAltId(); Assertions.assertEquals(firstResult, secondResult); verify(productBundleAltIdRepository, times(1)).getAllDistinctAltKeyIds(); }
6. 排除CI测试并行干扰
若CI环境并行执行测试,可能导致缓存被其他测试清理。尝试在CI中单独运行该测试类,确认是否因测试上下文复用导致问题。
内容的提问来源于stack exchange,提问作者Neretva
相关产品推荐
相关产品推荐

