Spring Cache @Cacheable - ne fonctionne pas tout en appelant d'une autre méthode de la même fève

cache à ressort ne fonctionne pas quand on appelle la méthode mise en cache d'une autre méthode de la même fève.

Voici un exemple pour expliquer mon problème clairement.

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

}

résultat:

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

l'appel de méthode getEmployeeData utilise le cache employeeData dans le second appel comme devrait. Mais lorsque la méthode getEmployeeData est appelée dans la classe AService (dans getEmployeeEnrichedData ), le Cache n'est pas utilisé.

est-ce que c'est comme ça que fonctionne la cache de printemps ou est-ce que je manque quelque chose ?

46
demandé sur Vladimir Vagaytsev 2013-06-03 18:51:54

7 réponses

je crois que c'est comme ça que ça marche. D'après ce que je me souviens avoir lu, il y a une classe proxy générée qui intercepte toutes les requêtes et répond avec la valeur en cache, mais les appels "internes" dans la même classe n'obtiendront pas la valeur en cache.

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

seuls les appels de méthode externes provenant du mandataire sont intercepter. Cela signifie que l'auto-invocation, en effet, une méthode dans l'objet cible appelant une autre méthode de l'objet cible, ne conduira pas à une réelle interception de cache à l'exécution même si le la méthode invoquée est marquée avec @Cacheable.

80
répondu Shawn D. 2016-01-04 12:19:18

l'exemple ci-dessous est ce que j'utilise pour frapper le proxy de l'intérieur de la même fève, il est similaire à la solution de @mario-eis, mais je le trouve un peu plus lisible (peut-être ce n'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 opération dans Spring bean

13
répondu molholm 2017-05-23 12:02:40

depuis le printemps 4.3 le problème pourrait être résolu en utilisant auto-auto-allumage 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);
    }
}
10
répondu radistao 2018-02-19 13:16:22

voici ce que je fais pour les petits projets avec une utilisation marginale des appels de méthodes dans la même classe. La documentation en code est fortement déconseillée, car elle peut sembler confuse aux collègues. Mais son facile à tester, simple, rapide à réaliser et me épargne l'instrumentation AspectJ complète. Cependant, pour l'usage plus lourd je conseillerais la solution D'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);
        ...
    }
}
7
répondu Mario Eis 2015-12-04 15:03:46

utilisez le tissage statique pour créer un proxy autour de votre haricot. Dans ce cas, même les méthodes "internes" fonctionneraient correctement

2
répondu Dewfy 2013-06-03 15:15:25

une autre solution simple et bonne: déplacer @Cacheable annotation au niveau inférieur (DAO). Le problème reste à l'intérieur de la classe DAO, mais résolu pour le Service.

0
répondu GKislin 2016-02-16 17:03:13

j'utilise la fève intérieure interne ( FactoryInternalCache ) avec une véritable cache à cet effet:

@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="/q/spring-cache-cacheable-not-working-while-calling-from-another-method-of-the-same-bean-33877/">Spring Cache @Cacheable - not working while calling from another method of the same bean</a>
 * @see <a href="/q/spring-cache-cacheable-method-ignored-when-called-from-within-the-same-class-48025/">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
répondu radistao 2018-01-09 13:00:12