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

时间:2016-08-22 05:59:57

标签: caching spring-boot ehcache

我在@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);

它应该像这样工作:

  • 如果数据存在,则获取值表格,
  • 如果没有从枚举中保存价值。

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

我的缓存配置:

@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);

并且它运作良好。

我在做什么?

2 个答案:

答案 0 :(得分:17)

您的SettingsService中有两种方法,一种是缓存的(getGlobalEnumValue(...)),另一种是未缓存的方法,但调用另一种方法(getGlobalSettingsValue(...))。

Spring缓存抽象的工作方式是代理您的类(使用Spring AOP)。但是,对同一类中的方法的调用不会调用代理逻辑,而是调用下面的直接业务逻辑。这意味着如果您在同一个bean中调用方法,则缓存不起作用。

因此,如果您正在调用getGlobalSettingsValue(),那么当该方法调用getGlobalEnumValue(...)时,它将不会填充,也不会使用缓存。

可能的解决方案是:

  1. 使用代理时不要在同一个类中调用另一个方法
  2. 也可以缓存其他方法
  3. 使用AspectJ而不是Spring AOP,它在编译时将代码直接编织到字节代码中,而不是代理类。您可以通过设置@EnableCaching(mode = AdviceMode.ASPECTJ)来切换模式。但是,您也必须set up load time weaving
  4. 将服务自动装入您的服务,并使用该服务而不是直接调用该方法。通过自动装配服务,您可以将代理注入您的服务。

答案 1 :(得分:8)

问题在于您调用可缓存方法的位置。当您从同一个类调用@Cacheable方法时,只需从this引用调用它,这意味着它不会被Spring的代理包装,因此Spring无法捕获您的调用来处理它。 / p>

关于解决这个问题的方法之一是@Autowired为自己提供服务,并且只需调用您希望春天必须通过此引用处理的方法:

@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的方法移动到专用类。