You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 01:53:16