Spring Cache @Cacheable - non funziona durante la chiamata da un altro metodo dello stesso bean


107

Spring cache non funziona quando si chiama un metodo memorizzato nella cache da un altro metodo dello stesso bean.

Ecco un esempio per spiegare in modo chiaro il mio problema.

Configurazione:

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

Servizio memorizzato nella cache:

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

}

Risultato:

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

La getEmployeeDatachiamata al metodo utilizza la cache employeeDatanella seconda chiamata come previsto. Ma quando il getEmployeeDatametodo viene chiamato all'interno della AServiceclasse (in getEmployeeEnrichedData), Cache non viene utilizzato.

È così che funziona Spring cache o mi manca qualcosa?


usi lo stesso valore per someDateparam?
Dewfy

@Dewfy Sì, è lo stesso
Bala

Risposte:


158

Credo che funzioni così. Da quello che ricordo di aver letto, c'è una classe proxy generata che intercetta tutte le richieste e risponde con il valore memorizzato nella cache, ma le chiamate "interne" all'interno della stessa classe non riceveranno il valore memorizzato nella cache.

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

Vengono intercettate solo le chiamate a metodi esterni in arrivo tramite il proxy. Ciò significa che l'auto-invocazione, in effetti, un metodo all'interno dell'oggetto di destinazione che chiama un altro metodo dell'oggetto di destinazione, non porterà a un'effettiva intercettazione della cache in fase di esecuzione anche se il metodo richiamato è contrassegnato con @Cacheable.


1
Bene, se effettui anche la seconda chiamata Cacheable, mancherà solo una cache. Cioè, solo la prima chiamata a getEmployeeEnrichedData ignorerà la cache. La seconda chiamata a esso avrebbe utilizzato il ritorno memorizzato nella cache in precedenza dalla prima chiamata a getEmployeeEnrichedData.
Shawn D.

1
@Bala Ho lo stesso problema, la mia soluzione è passare @Cacheablea DAO :( Se hai una soluzione migliore per favore fatemelo sapere, grazie.
VAdaihiep

2
puoi anche scrivere un servizio, ad esempio CacheService e mettere tutti i tuoi metodi di cache nel servizio. Autowire il servizio dove è necessario e chiamare i metodi. Aiutato nel mio caso.
DOUBL3P

Poiché Spring 4.3 questo potrebbe essere risolto utilizzando @Resourceauto-autowiring, vedi esempio stackoverflow.com/a/48867068/907576
radistao

1
Anche il @Cacheablemetodo esterno dovrebbe essere public, non funziona sui metodi privati ​​del pacchetto. L'ho trovato nel modo più duro.
Anand

36

Poiché Spring 4.3 il problema potrebbe essere risolto utilizzando auto-autowiring over @Resourcenota:

@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
Ho provato sotto 4.3.17e non ha funzionato, le chiamate per selfnon passare attraverso un proxy e la cache è (ancora) bypassata.
Madbreaks

Ha funzionato per me. Hit nella cache. Uso le ultime dipendenze primaverili a partire da questa data.
Tomas Bisciak

Sono l'unico a pensare che questo rompe gli schemi, sembra un mix singleton, ecc ecc?
2mia

ho usato la versione iniziale dello stivale a molla - 2.1.0.RELEASE, e ho avuto lo stesso problema. Questa particolare soluzione ha funzionato a meraviglia.
Deepan Prabhu Babu

18

L'esempio seguente è quello che uso per colpire il proxy dall'interno dello stesso bean, è simile alla soluzione di @ mario-eis, ma lo trovo un po 'più leggibile (forse non lo è :-). Ad ogni modo, mi piace mantenere le annotazioni @Cacheable al livello di servizio:

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

Vedi anche Avvio di una nuova transazione in Spring bean


1
L'accesso al contesto dell'applicazione, ad esempio applicationContext.getBean(SettingService.class);, è l'opposto dell'inserimento delle dipendenze. Suggerisco di evitare quello stile.
SingleShot

2
Sì, sarebbe meglio evitarlo, ma non vedo una soluzione migliore per questo problema.
molholm

10

Ecco cosa faccio per piccoli progetti con un utilizzo solo marginale delle chiamate di metodo all'interno della stessa classe. La documentazione in-code è fortemente consigliata, in quanto potrebbe sembrare complicata ai colleghi. Ma è facile da testare, semplice, veloce da ottenere e mi risparmia la strumentazione AspectJ in piena regola. Tuttavia, per un utilizzo più intenso, consiglierei la soluzione 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
potresti fare un esempio con AspectJ?
Sergio Bilello

Questa risposta è un duplicato di stackoverflow.com/a/34090850/1371329 .
jaco0646

3

Nel mio caso aggiungo variabile:

@Autowired
private AService  aService;

Quindi chiamo il getEmployeeDatametodo usando ilaService

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

}

In questo caso utilizzerà la cache.


2

Usa la tessitura statica per creare un proxy attorno al tuo fagiolo. In questo caso anche i metodi "interni" funzionerebbero correttamente


Cos'è la "tessitura statica"? google non aiuta molto. Qualche suggerimento per comprendere questi concetti?
Bala

@Bala - solo per esempio sul nostro progetto usiamo il <iajccompilatore (da ant) ​​che risolve tutti gli aspetti di necessità per le classi abilitate alla cache.
Dewfy

0

Uso il bean interno interno ( FactoryInternalCache) con cache reale per questo scopo:

@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

la soluzione di gran lunga più semplice è solo fare riferimento in questo modo:

AService.this.getEmployeeData(date);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.