177 votes

Spring Cache @Cacheable - ne fonctionne pas lors d'un appel depuis une autre méthode du même bean

Le cache de Spring ne fonctionne pas lorsque l'on appelle une méthode mise en cache depuis une autre méthode du même bean.

Voici un exemple pour expliquer mon problème de manière claire.

Configuration :

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

Service en cachette :

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

}

Résultat :

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

El getEmployeeData l'appel de la méthode utilise le cache employeeData dans le deuxième appel comme prévu. Mais lorsque le getEmployeeData est appelée dans le cadre de la méthode AService (en getEmployeeEnrichedData ), le cache n'est pas utilisé.

Est-ce que c'est comme ça que fonctionne le cache de Spring ou est-ce que je rate quelque chose ?

248voto

Shawn D. Points 1881

Je crois que c'est ainsi que cela fonctionne. D'après ce que je me souviens avoir lu, une classe proxy est générée pour intercepter toutes les demandes et répondre avec la valeur mise en cache, mais les appels "internes" au sein de la même classe n'obtiennent pas la valeur mise en cache.

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

Seuls les appels de méthodes externes arrivant par le proxy sont interceptés. Cela signifie que l'auto-invocation, en fait, une méthode au sein de l'objet cible appelant une autre méthode de l'objet cible, n'entraînera pas d'interception du cache au moment de l'exécution, même si la méthode invoquée méthode invoquée est marquée @Cacheable.

60voto

radistao Points 1070

Depuis Spring 4.3, le problème a pu être résolu en utilisant auto-alimentation sur @Resource annotation :

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

23voto

molholm Points 166

L'exemple ci-dessous est celui que j'utilise pour atteindre le proxy à partir du même bean, il est similaire à la solution de @mario-eis, mais je le trouve un peu plus lisible (peut-être qu'il ne l'est pas :-). Quoi qu'il en soit, j'aime garder les annotations @Cacheable au niveau du service :

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

Voir aussi Démarrage d'une nouvelle transaction dans un haricot de Spring

13voto

Mario Eis Points 81

Voici ce que je fais pour les petits projets avec une utilisation marginale d'appels de méthodes au sein d'une même classe. La documentation dans le code est fortement conseillée, car elle peut sembler étrange aux collègues. Mais c'est facile à tester, simple, rapide à réaliser et cela m'épargne l'instrumentation complète d'AspectJ. Cependant, pour un usage plus intensif, je conseillerais la solution 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);
        ...
    }
}

12voto

Vijay Points 121

Si vous appelez une méthode en cache depuis le même haricot, elle sera traitée comme une méthode privée et les annotations seront ignorées.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X