Spring Boot中@Service类的缓存不起作用

15

我在@Service方法中保存某些值时遇到了问题。 我的代码:

@Service(value = "SettingsService")
public class SettingsService {
...

    public String getGlobalSettingsValue(Settings setting) {
        getTotalEhCacheSize();
        if(!setting.getGlobal()){
            throw new IllegalStateException(setting.name() + " is not global setting");
        }
        GlobalSettings globalSettings = globalSettingsRepository.findBySetting(setting);
        if(globalSettings != null)
            return globalSettings.getValue();
        else
            return getGlobalEnumValue(setting)
    }

@Cacheable(value = "noTimeCache", key = "#setting.name()")
    public String getGlobalEnumValue(Settings setting) {
        return Settings.valueOf(setting.name()).getDefaultValue();
    }

我的存储库类:

@Repository
public interface GlobalSettingsRepository extends CrudRepository<GlobalSettings, Settings> {

    @Cacheable(value = "noTimeCache", key = "#setting.name()", unless="#result == null")
    GlobalSettings findBySetting(Settings setting);

应该像这样工作:

  • 如果数据存在,从数据库中获取值,
  • 如果不存在,则保存枚举值。

但它没有保存任何来自数据库或枚举的数据。

我的缓存配置:

@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    public EhCacheCacheManager cacheManager(CacheManager cm) {
        return new EhCacheCacheManager(cm);
    }
    @Bean
    public EhCacheManagerFactoryBean ehcache() {
        EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();
        ehCacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));

        return  ehCacheManagerFactoryBean;
    }
}

我有一些示例来确保在我的rest方法项目中缓存正在起作用:

    @RequestMapping(value = "/system/status", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> systemStatus() {
        Object[] list = userPuzzleRepository.getAverageResponseByDateBetween(startDate, endDate);
...
}

public interface UserPuzzleRepository extends CrudRepository<UserPuzzle, Long> {
    @Cacheable(value = "averageTimeAnswer", key = "#startDate")
    @Query("select AVG(case when up.status='SUCCESS' OR up.status='FAILURE' OR up.status='TO_CHECK' then up.solvedTime else null end) from UserPuzzle up where up.solvedDate BETWEEN ?1 AND ?2")
    Object[] getAverageResponseByDateBetween(Timestamp startDate, Timestamp endDate);

并且它工作得很好。

我做错了什么?

3个回答

42

你的SettingsService中有两种方法,一种是缓存的(getGlobalEnumValue(...)),另一种不缓存,但会调用另一个方法(getGlobalSettingsValue(...))。

然而,Spring缓存抽象的工作原理是通过代理你的类(使用Spring AOP)。但是,对于同一类中的方法调用将不会调用代理逻辑,而是直接调用业务逻辑。这意味着如果你在同一bean中调用方法,则缓存不起作用。

因此,如果你调用了getGlobalSettingsValue(),当该方法调用getGlobalEnumValue(...)时,它不会填充或使用缓存。


可能的解决方案包括:

  1. 在使用代理时不要调用同一类中的另一个方法
  2. 同样对另一个方法进行缓存
  3. 使用AspectJ而不是Spring AOP,在编译时直接将代码编织到字节码中,而不是代理类。你可以通过设置@EnableCaching(mode = AdviceMode.ASPECTJ)来切换模式。但是,你还需要设置加载时编织
  4. 将服务自动装配到你的服务中,并使用该服务而不是直接调用方法。通过自动装配服务,你将代理注入到你的服务中。

@g00glen00b救了我的一天! - Spring
2
尊敬的先生,您在调试了10个小时后,让我感到无比的解脱。我不知道为什么这在Spring文档中没有提到。 - Manish Bansal
非常感谢!您能告诉我在哪里找到了解决方案吗? - ccjli

9
问题出在你调用可缓存方法的地方。当你从同一个类中调用 @Cacheable 方法时,你只是从 this 引用中调用它,这意味着它没有被Spring的代理包装, 所以Spring无法捕获你的调用来处理它。
解决此问题的一种方法是将服务 @Autowired 到其自身,并通过此引用调用期望Spring处理的方法。
@Service(value = "SettingsService")
public class SettingsService {
//...

    @Autowired
    private SettingsService settingsService;
//...
    public String getGlobalSettingsValue(Settings setting) {
       // ...
        return settingsSerive.getGlobalEnumValue(setting)
//-----------------------^Look Here
    }

    @Cacheable(value = "noTimeCache", key = "#setting.name()")
    public String getGlobalEnumValue(Settings setting) {
        return Settings.valueOf(setting.name()).getDefaultValue();
    }
}

但是,如果你遇到这样的问题,那么这意味着你的类所承担的任务过多,不符合“单一职责原则”。更好的解决方法是将带有@Cacheable注解的方法移动到专门的类中。


2
自动装配不会导致递归问题吗? - Don Rhummy

0
@Cacheable的方法应该直接从控制器/服务中调用。如果您正在调用内部调用缓存方法的方法A,它将不起作用。
您的服务类
@Cacheable("appToken")
public String cachedMethod(){
    return "";
}
    

public void notCachedMethod(PostEventPayload requestPayload){
   getAppTokenAA();
}

您的控制器: 工作中:
eventService.cachedMethod();

不起作用:

eventService.notCachedMethod()

如果您直接访问,Spring Boot 将在内部将其存储在缓存中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接