Spring Cache @Cacheable-동일한 Bean의 다른 메소드에서 호출하는 동안 작동하지 않음


107

동일한 Bean의 다른 메소드에서 캐시 된 메소드를 호출 할 때 Spring 캐시가 작동하지 않습니다.

다음은 내 문제를 명확하게 설명하는 예입니다.

구성 :

<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) Cache가 사용되지 않습니다.

이것이 스프링 캐시가 작동하는 방식입니까 아니면 내가 뭔가를 놓치고 있습니까?


someDateparam에 동일한 값을 사용하고 있습니까?
Dewfy 2013-06-03

예 @Dewfy, 그것은 동일합니다
발라

답변:


158

나는 이것이 작동하는 방식이라고 믿습니다. 내가 읽은 내용에서 모든 요청을 가로 채고 캐시 된 값으로 응답하는 프록시 클래스가 생성되었지만 동일한 클래스 내의 '내부'호출은 캐시 된 값을 얻지 못합니다.

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

프록시를 통해 들어오는 외부 메서드 호출 만 차단됩니다. 즉, 자체 호출은 실제로 대상 개체의 다른 메서드를 호출하는 대상 개체 내의 메서드가 호출 된 메서드가 @Cacheable로 표시되어 있더라도 런타임에 실제 캐시 가로 채기로 이어지지 않음을 의미합니다.


1
두 번째 호출도 Cacheable로 설정하면 캐시 미스가 하나만 발생합니다. 즉, getEmployeeEnrichedData에 대한 첫 번째 호출 만 캐시를 우회합니다. 두 번째 호출은 getEmployeeEnrichedData에 대한 첫 번째 호출에서 이전에 캐시 된 리턴을 사용했습니다.
Shawn D.

1
@Bala 같은 문제가 있습니다. 제 해결책은 @CacheableDAO 로 이동 하는 것입니다. (더 나은 해결책이 있으면 알려주세요. 감사합니다.
VAdaihiep

2
또한 CacheService와 같은 서비스를 작성하고 모든 캐시 메소드를 서비스에 넣을 수 있습니다. 필요한 곳에 서비스를 자동 연결하고 메서드를 호출합니다. 제 경우를 도왔습니다.
DOUBL3P

Spring 4.3 이후이 문제는 @Resource자체 자동
배선을

1
또한 외부 @Cacheable메서드는이어야하며 public패키지 전용 메서드에서는 작동하지 않습니다. 어려운 길을 찾았습니다.
anand

36

Spring 4.3부터 주석에 대한 자체 자동 배선 을 사용하여 문제를 해결할 수 있습니다 @Resource.

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

2
이것을 시도 4.3.17했지만 작동 self하지 않았고 프록시를 거치지 않고 캐시가 (여전히) 우회됩니다.
Madbreaks

나를 위해 일했습니다. 캐시 적중. 이 날짜를 기준으로 최신 스프링 종속성을 사용합니다.
Tomas Bisciak

이것이 패턴을 깨고 싱글 톤 믹스처럼 보인다고 생각하는 유일한 사람입니까?
2mia

스프링 부트 스타터 버전-2.1.0.RELEASE를 사용했는데 같은 문제가 발생했습니다. 이 특정 솔루션은 매력처럼 작동했습니다.
Deepan Prabhu Babu

18

아래 예제는 동일한 빈 내에서 프록시를 누르는 데 사용하는 것입니다. @ 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에서 새 트랜잭션 시작 도 참조하십시오.


1
예를 들어 애플리케이션 컨텍스트에 액세스하는 applicationContext.getBean(SettingService.class);것은 종속성 주입과 반대입니다. 나는 그 스타일을 피하는 것이 좋습니다.
SINGLESHOT

2
예, 그것을 피하는 것이 더 낫지 만이 문제에 대한 더 나은 해결책을 찾지 못했습니다.
molholm

10

다음은 동일한 클래스 내에서 메서드 호출을 약간만 사용하는 소규모 프로젝트에 대해 수행하는 작업입니다. 코드 내 문서는 동료들에게 어려움을 겪을 수 있으므로 강력하게 권장됩니다. 그러나 테스트하기 쉽고, 간단하고, 빠르게 달성 할 수 있으며, 완벽한 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);
        ...
    }
}

1
AspectJ에 대한 예를 들어 줄 수 있습니까?
Sergio Bilello 2016 년

이 답변은 stackoverflow.com/a/34090850/1371329 의 중복입니다 .
jaco0646

3

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

@Autowired
private AService  aService;

나는 전화를 그래서 getEmployeeData를 사용하여 방법을aService

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

}

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


2

정적 위빙을 사용하여 빈 주변에 프록시를 만듭니다. 이 경우 '내부'방법도 올바르게 작동합니다.


"정적 직조"란 무엇입니까? Google은별로 도움이되지 않습니다. 이 개념을 이해하기위한 포인터가 있습니까?
Bala

@Bala-예를 들어 우리 프로젝트 <iajc에서는 캐시 가능 클래스에 대한 모든 필수 측면을 해결 하는 컴파일러 (ant)를 사용합니다.
Dewfy 2013-06-04

0

FactoryInternalCache이 목적을 위해 실제 캐시와 함께 내부 내부 빈 ( )을 사용합니다.

@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="/programming/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="/programming/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);
    }
}
}

0

지금까지 가장 쉬운 해결책은 다음과 같이 참조하는 것입니다.

AService.this.getEmployeeData(date);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.