복붙노트

[SPRING] Spring Cache @Cacheable - 같은 빈의 다른 메소드에서 호출하는 동안 작동하지 않는다.

SPRING

Spring Cache @Cacheable - 같은 빈의 다른 메소드에서 호출하는 동안 작동하지 않는다.

같은 bean의 다른 메소드에서 캐시 된 메소드를 호출 할 때 스프링 캐시가 작동하지 않습니다.

여기 내 문제를 명확한 방법으로 설명하는 예제가 있습니다.

구성 :

<cache:annotation-driven cache-manager="myCacheManager" />

<bean id="myCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
    <property name="cacheManager" ref="myCache" />
</bean>

<!-- Ehcache library setup -->
<bean id="myCache"
    class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:shared="true">
    <property name="configLocation" value="classpath:ehcache.xml"></property>
</bean>

<cache name="employeeData" maxElementsInMemory="100"/>  

캐시 된 서비스 :

@Named("aService")
public class AService {

    @Cacheable("employeeData")
    public List<EmployeeData> getEmployeeData(Date date){
    ..println("Cache is not being used");
    ...
    }

    public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
        List<EmployeeData> employeeData = getEmployeeData(date);
        ...
    }

}

결과 :

aService.getEmployeeData(someDate);
output: Cache is not being used
aService.getEmployeeData(someDate); 
output: 
aService.getEmployeeEnrichedData(someDate); 
output: Cache is not being used

getEmployeeData 메서드 호출은 두 번째 호출에서 employeeData 캐시를 예상대로 사용합니다. 그러나 getEmployeeData 메소드가 AService 클래스 (getEmployeeEnrichedData에 있음) 내에서 호출되면 캐시가 사용되지 않습니다.

스프링 캐쉬가 작동하는 방식인가요, 뭔가 빠진 것입니까?

해결법

  1. ==============================

    1.나는 이것이 그것이 어떻게 작동하는지 믿습니다. 내가 읽은 것을 보면, 모든 요청을 차단하고 캐시 된 값으로 응답하는 프록시 클래스가 생성되지만 동일한 클래스 내의 '내부'호출은 캐시 된 값을 가져 오지 않습니다.

    나는 이것이 그것이 어떻게 작동하는지 믿습니다. 내가 읽은 것을 보면, 모든 요청을 차단하고 캐시 된 값으로 응답하는 프록시 클래스가 생성되지만 동일한 클래스 내의 '내부'호출은 캐시 된 값을 가져 오지 않습니다.

    https://code.google.com/p/ehcache-spring-annotations/wiki/UsingCacheable에서

  2. ==============================

    2.아래 예제는 같은 빈에서 프록시를 치기 위해 사용하는 예제입니다. @ mario-eis '솔루션과 비슷하지만 좀 더 읽기 쉽습니다 (아마도 그렇지는 않습니다 :-). 어쨌든 서비스 수준에서 @Cacheable 주석을 유지하고 싶습니다.

    아래 예제는 같은 빈에서 프록시를 치기 위해 사용하는 예제입니다. @ mario-eis '솔루션과 비슷하지만 좀 더 읽기 쉽습니다 (아마도 그렇지는 않습니다 :-). 어쨌든 서비스 수준에서 @Cacheable 주석을 유지하고 싶습니다.

    @Service
    @Transactional(readOnly=true)
    public class SettingServiceImpl implements SettingService {
    
    @Inject
    private SettingRepository settingRepository;
    
    @Inject
    private ApplicationContext applicationContext;
    
    @Override
    @Cacheable("settingsCache")
    public String findValue(String name) {
        Setting setting = settingRepository.findOne(name);
        if(setting == null){
            return null;
        }
        return setting.getValue();
    }
    
    @Override
    public Boolean findBoolean(String name) {
        String value = getSpringProxy().findValue(name);
        if (value == null) {
            return null;
        }
        return Boolean.valueOf(value);
    }
    
    /**
     * Use proxy to hit cache 
     */
    private SettingService getSpringProxy() {
        return applicationContext.getBean(SettingService.class);
    }
    ...
    

    Spring bean에서 새로운 트랜잭션 시작하기

  3. ==============================

    3.Spring 4.3부터 @Resource 주석 위에 self-autowiring을 사용하면 문제를 해결할 수있다.

    Spring 4.3부터 @Resource 주석 위에 self-autowiring을 사용하면 문제를 해결할 수있다.

    @Component
    @CacheConfig(cacheNames = "SphereClientFactoryCache")
    public class CacheableSphereClientFactoryImpl implements SphereClientFactory {
    
        /**
         * 1. Self-autowired reference to proxified bean of this class.
         */
        @Resource
        private SphereClientFactory self;
    
        @Override
        @Cacheable(sync = true)
        public SphereClient createSphereClient(@Nonnull TenantConfig tenantConfig) {
            // 2. call cached method using self-bean
            return self.createSphereClient(tenantConfig.getSphereClientConfig());
        }
    
        @Override
        @Cacheable(sync = true)
        public SphereClient createSphereClient(@Nonnull SphereClientConfig clientConfig) {
            return CtpClientConfigurationUtils.createSphereClient(clientConfig);
        }
    }
    
  4. ==============================

    4.같은 클래스 내에서 메소드 호출을 거의 사용하지 않는 소규모 프로젝트에서 내가하는 일은 다음과 같습니다. 동료에게 스트레스를 줄 수 있으므로 코드 내 문서가 강력하게 권고됩니다. 그러나 테스트하기 쉽고, 간단하고, 빠르게 달성 할 수 있으며, 완전히 손상된 AspectJ 장비를 절약 할 수 있습니다. 그러나, 더 많은 사용량에 대해 나는 AspectJ 솔루션에 대한 조언을 원한다.

    같은 클래스 내에서 메소드 호출을 거의 사용하지 않는 소규모 프로젝트에서 내가하는 일은 다음과 같습니다. 동료에게 스트레스를 줄 수 있으므로 코드 내 문서가 강력하게 권고됩니다. 그러나 테스트하기 쉽고, 간단하고, 빠르게 달성 할 수 있으며, 완전히 손상된 AspectJ 장비를 절약 할 수 있습니다. 그러나, 더 많은 사용량에 대해 나는 AspectJ 솔루션에 대한 조언을 원한다.

    @Service
    @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
    class AService {
    
        private final AService _aService;
    
        @Autowired
        public AService(AService aService) {
            _aService = aService;
        }
    
        @Cacheable("employeeData")
        public List<EmployeeData> getEmployeeData(Date date){
            ..println("Cache is not being used");
            ...
        }
    
        public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
            List<EmployeeData> employeeData = _aService.getEmployeeData(date);
            ...
        }
    }
    
  5. ==============================

    5.static weaving을 사용하여 bean 주위에 프록시를 작성하십시오. 이 경우에도 '내부'메소드가 올바르게 작동합니다.

    static weaving을 사용하여 bean 주위에 프록시를 작성하십시오. 이 경우에도 '내부'메소드가 올바르게 작동합니다.

  6. ==============================

    6.내 경우에는 변수를 추가합니다.

    내 경우에는 변수를 추가합니다.

    @Autowired
    private AService  aService;
    

    그래서 나는 Service를 사용하여 Employee Data 메소드를 호출한다.

    @Named("aService")
    public class AService {
    
    @Cacheable("employeeData")
    public List<EmployeeData> getEmployeeData(Date date){
    ..println("Cache is not being used");
    ...
    }
    
    public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
        List<EmployeeData> employeeData = aService.getEmployeeData(date);
        ...
    }
    

    }

    이 경우 캐시를 사용합니다.

  7. ==============================

    7.이 목적을 위해 내부 캐시 (FactoryInternalCache)와 내부 내부 bean을 사용합니다.

    이 목적을 위해 내부 캐시 (FactoryInternalCache)와 내부 내부 bean을 사용합니다.

    @Component
    public class CacheableClientFactoryImpl implements ClientFactory {
    
    private final FactoryInternalCache factoryInternalCache;
    
    @Autowired
    public CacheableClientFactoryImpl(@Nonnull FactoryInternalCache factoryInternalCache) {
        this.factoryInternalCache = factoryInternalCache;
    }
    
    /**
     * Returns cached client instance from cache.
     */
    @Override
    public Client createClient(@Nonnull AggregatedConfig aggregateConfig) {
        return factoryInternalCache.createClient(aggregateConfig.getClientConfig());
    }
    
    /**
     * Returns cached client instance from cache.
     */
    @Override
    public Client createClient(@Nonnull ClientConfig clientConfig) {
        return factoryInternalCache.createClient(clientConfig);
    }
    
    /**
     * Spring caching feature works over AOP proxies, thus internal calls to cached methods don't work. That's why
     * this internal bean is created: it "proxifies" overloaded {@code #createClient(...)} methods
     * to real AOP proxified cacheable bean method {@link #createClient}.
     *
     * @see <a href="https://stackoverflow.com/questions/16899604/spring-cache-cacheable-not-working-while-calling-from-another-method-of-the-s">Spring Cache @Cacheable - not working while calling from another method of the same bean</a>
     * @see <a href="https://stackoverflow.com/questions/12115996/spring-cache-cacheable-method-ignored-when-called-from-within-the-same-class">Spring cache @Cacheable method ignored when called from within the same class</a>
     */
    @EnableCaching
    @CacheConfig(cacheNames = "ClientFactoryCache")
    static class FactoryInternalCache {
    
        @Cacheable(sync = true)
        public Client createClient(@Nonnull ClientConfig clientConfig) {
            return ClientCreationUtils.createClient(clientConfig);
        }
    }
    }
    
  8. from https://stackoverflow.com/questions/16899604/spring-cache-cacheable-not-working-while-calling-from-another-method-of-the-s by cc-by-sa and MIT license