source

@Service 클래스의 스프링 부트 캐싱이 작동하지 않음

lovecheck 2023. 10. 29. 19:48
반응형

@Service 클래스의 스프링 부트 캐싱이 작동하지 않음

@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에서 값을 가져옵니다.
  • enum에서 값을 저장하지 않는 경우.

DB나 enum에서 어떤 데이터도 저장하지 않았습니다.

캐시 구성:

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

캐시가 프로젝트에서 정지 방식으로 작동하는지 확인할 수 있는 몇 가지 예가 있습니다.

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

효과가 좋습니다.

내가 뭘 잘못하고 있는 거지?

두 가지 방법이 있습니다.SettingsService, 캐시된 하나(getGlobalEnumValue(...)) 및 캐시되지 않은 다른 하나를 호출하지만 다른 방법을 호출합니다 (getGlobalSettingsValue(...)).

그러나 Spring 캐시 추상화가 작동하는 방법은 클래스를 프록시(Spring AOP 사용)하는 것입니다.그러나 동일한 클래스 내의 메소드에 대한 호출은 프록시 논리를 호출하는 것이 아니라 아래의 직접적인 비즈니스 논리를 호출합니다.즉, 동일한 빈에서 메서드를 호출하는 경우에는 캐싱이 작동하지 않습니다.

그래서, 전화하시는 분들은getGlobalSettingsValue(), 메워지지 않으며, 해당 메서드가 호출할 때 캐시를 사용하지 않습니다.getGlobalEnumValue(...).


가능한 해결책은 다음과 같습니다.

  1. 프록시를 사용할 때 같은 클래스의 다른 메서드를 호출하지 않음
  2. 다른 방법 캐싱도 가능합니다.
  3. Spring AOP 대신 Aspec J를 사용하여 클래스를 프록시하는 대신 컴파일 시 바이트 코드로 직접 코드를 위빙합니다.를 설정하여 모드를 전환할 수 있습니다.@EnableCaching(mode = AdviceMode.ASPECTJ). 그러나, 당신은 짐을 짜는 시간 또한 설정해야 할 것입니다.
  4. 서비스를 자동으로 서비스에 연결하고 메소드를 직접 호출하지 않고 해당 서비스를 사용합니다.서비스를 자동으로 배선함으로써 프록시를 서비스에 주입합니다.

문제는 캐시 가능 메서드를 호출하는 위치에 있습니다.당신이 당신의 전화를 걸때@Cacheable같은 클래스의 메소드, 당신은 그냥 그것을 호출합니다.this봄의 대리인으로 감싸지지 않는다는 뜻입니다 그래서 봄은 당신의 요청을 받아낼 수가 없습니다.

이 문제를 해결하는 방법 중 하나는@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전담반으로 보내겠습니다.

@Cacheable 메서드는 컨트롤러/서비스에서 직접 호출해야 합니다.만약 당신이 내부적으로 cacheable method를 호출하는 method A를 호출한다면, 그것은 작동하지 않을 것입니다.

서비스 클래스

@Cacheable("appToken")
public String cachedMethod(){
    return "";
}
    

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

컨트롤러: 작동 중:

eventService.cachedMethod();

작동하지 않음:

eventService.notCachedMethod()

직접 액세스하는 경우 스프링 부트가 내부적으로 캐시에 저장됩니다.

언급URL : https://stackoverflow.com/questions/39072235/spring-boot-caching-in-service-class-does-not-work

반응형