Spring Cache @Cacheable - aynı çekirdeğin başka bir yönteminden çağrı yapılırken çalışmıyor


109

Aynı fasulyenin başka bir yönteminden önbelleğe alınmış yöntem çağrılırken yay önbelleği çalışmıyor.

İşte sorunumu net bir şekilde açıklamak için bir örnek.

Yapılandırma:

<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"/>  

Önbelleğe alınmış hizmet:

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

}

Sonuç:

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

getEmployeeDataYöntem çağrısı kullandığı önbelleğe employeeDatabeklendiği gibi ikinci aramada. Ancak getEmployeeDatayöntem AServicesınıf (in getEmployeeEnrichedData) içinde çağrıldığında , Cache kullanılmıyor.

Yay önbelleği böyle mi çalışıyor yoksa bir şey mi kaçırıyorum?


someDateparam için aynı değeri mi kullanıyorsunuz ?
Dewfy

@Dewfy Evet, aynı
Bala

Yanıtlar:


162

Bunun nasıl çalıştığına inanıyorum. Okuduğum kadarıyla, tüm istekleri kesen ve önbelleğe alınmış değerle yanıt veren bir proxy sınıfı var, ancak aynı sınıf içindeki 'dahili' çağrılar önbelleğe alınan değeri almayacak.

Gönderen https://code.google.com/p/ehcache-spring-annotations/wiki/UsingCacheable

Yalnızca proxy aracılığıyla gelen harici yöntem çağrıları durdurulur. Bu, kendiliğinden başlatmanın, hedef nesnenin başka bir yöntemini çağıran hedef nesne içindeki bir yöntemin, çağrılan yöntem @Cacheable ile işaretlenmiş olsa bile, çalışma zamanında gerçek bir önbellek müdahalesine yol açmayacağı anlamına gelir.


1
İkinci aramayı da Önbelleğe Alınabilir yaparsanız, yalnızca bir önbelleği kaçırırsınız. Yani, yalnızca getEmployeeEnrichedData'ya yapılan ilk çağrı önbelleği atlayacaktır. İkinci çağrı, getEmployeeEnrichedData'ya ilk çağrıdan önceden önbelleğe alınmış dönüşü kullanırdı.
Shawn D.

1
@Bala Aynı sorunum var, çözümüm DAO'ya geçmek @Cacheable:( Daha iyi bir çözümünüz varsa lütfen bana bildirin, teşekkürler.
VAdaihiep

2
ayrıca CacheService gibi bir Hizmet yazabilir ve tüm yöntemlerinizi önbelleğe almak için hizmete koyabilirsiniz. İhtiyaç duyduğunuz yerde Hizmeti otomatik olarak bağlayın ve yöntemleri çağırın. Benim durumumda yardımcı oldu.
DOUBL3P

1
Bahar 4.3'ten bu yana, bu @Resourcekendi kendine otomatik kablolama kullanılarak çözülebilir , örnek stackoverflow.com/a/48867068/907576
radistao

1
Ayrıca harici @Cacheableyöntem de olmalı public, özel paket yöntemlerinde çalışmıyor. Zor yoldan buldum.
Anand

38

İlkbahar 4.3'ten bu yana sorun, açıklama üzerinden kendinden otomatik kablolama kullanılarak çözülebilir @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.17Bunu denedim ve işe yaramadı, selfbir proxy'den geçmeme çağrıları ve önbellek (hala) atlandı.
Madbreaks

Benim için çalıştı. Önbellek isabetleri. Bu tarihten itibaren son bahar bağımlılıklarını kullanıyorum.
Tomas Bisciak

Bunun kalıpları kırdığını, bir singleton karışımı gibi göründüğünü vb. düşünen tek kişi ben miyim?
2019

Spring boot starter version - 2.1.0.RELEASE kullandım ve aynı sorunu yaşadım. Bu özel çözüm bir cazibe gibi çalıştı.
Deepan Prabhu Babu

Will, döngüsel bir bağımlılık yaratmıyor mu?
Chandresh Mishra

18

Aşağıdaki örnek, proxy'yi aynı fasulye içinden vurmak için kullandığım şeydir, @ mario-eis'in çözümüne benzer, ancak onu biraz daha okunaklı buluyorum (belki :-) değil. Her neyse, @Cacheable ek açıklamalarını hizmet düzeyinde tutmayı seviyorum:

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

Ayrıca bkz . Bahar fasulyesinde yeni işlem başlatmak


1
Uygulama bağlamına erişim, örneğin applicationContext.getBean(SettingService.class);, bağımlılık enjeksiyonunun tersidir. Bu tarzdan kaçınmanızı öneririm.
SingleShot

2
Evet, bundan kaçınmak daha iyi olur, ancak bu sorun için daha iyi bir çözüm göremiyorum.
molholm

10

İşte aynı sınıf içinde yöntem çağrılarının sadece marjinal kullanımı olan küçük projeler için yaptığım şey. Meslektaşlara garip görünebileceği için kod içi dokümantasyon şiddetle tavsiye edilir. Ancak test etmesi kolay, basit, elde etmesi hızlı ve tam gelişmiş AspectJ enstrümantasyonundan kurtulmamı sağlıyor. Ancak, daha yoğun kullanım için AspectJ çözümünü tavsiye ederim.

@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 ile bir örnek verebilir misiniz?
Sergio Bilello

Bu cevap, stackoverflow.com/a/34090850/1371329'un bir kopyasıdır .
jaco0646

3

Benim durumumda değişken ekliyorum:

@Autowired
private AService  aService;

Bu yüzden getEmployeeDatayöntemi kullanarakaService

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

}

Bu durumda önbelleği kullanacaktır.


2

Fasulyenizin etrafında proxy oluşturmak için statik dokumayı kullanın. Bu durumda 'dahili' yöntemler bile doğru şekilde çalışacaktır.


"Statik dokuma" nedir? google pek yardımcı olmuyor. Bu kavramları anlamak için herhangi bir işaret var mı?
Bala

@Bala - örneğin projemizde <iajc, önbelleğe alınabilir sınıflar için tüm gereklilik yönlerini çözen derleyici ( ant'tan ) kullanıyoruz.
Dewfy

0

FactoryInternalCacheBu amaç için gerçek önbellekli dahili iç fasulye ( ) kullanıyorum :

@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

Şimdiye kadarki en kolay çözüm şunun gibi referans vermektir:

AService.this.getEmployeeData(date);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.