simple implementations ejemplo cacheable cache annotation java spring caching ehcache

java - ejemplo - spring cache implementations



Spring Cache @Cacheable-no funciona mientras se llama desde otro método del mismo bean (7)

Creo que así es como funciona. De lo que recuerdo haber leído, se generó una clase de proxy que intercepta todas las solicitudes y responde con el valor en caché, pero las llamadas "internas" dentro de la misma clase no obtendrán el valor en caché.

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

Solo las llamadas a métodos externos que ingresan a través del proxy son interceptadas. Esto significa que la auto invocación, en efecto, un método dentro del objeto objetivo que llama a otro método del objeto objetivo, no conducirá a una intercepción de caché real en el tiempo de ejecución incluso si el método invocado está marcado con @Cacheable.

La memoria caché de Spring no funciona cuando se llama a un método en caché desde otro método del mismo bean.

Aquí hay un ejemplo para explicar mi problema de manera clara.

Configuración:

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

Servicio en caché:

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

Resultado:

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

La getEmployeeData método getEmployeeData utiliza cache employeeData en la segunda llamada como se esperaba. Pero cuando se getEmployeeData método getEmployeeData dentro de la clase AService (en getEmployeeEnrichedData ), Cache no se está utilizando.

¿Es así como funciona el caché de primavera o me falta algo?


Desde Spring 4.3, el problema se pudo resolver usando la self-autowiring sobre la anotación @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); } }


El siguiente ejemplo es lo que utilizo para ingresar al proxy desde dentro del mismo bean, es similar a la solución de @mario-eis, pero me parece un poco más legible (quizás no sea :-). De todos modos, me gusta mantener las anotaciones @Cacheable en el nivel de servicio:

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

Consulte también Iniciar nueva transacción en Spring Bean


Esto es lo que hago para proyectos pequeños con solo el uso marginal de llamadas a métodos dentro de la misma clase. La documentación dentro del código es altamente recomendada, ya que puede parecer extraño para los colegas. Pero es fácil de probar, simple, rápido de lograr y me ahorra la instrumentación AspectJ completa. Sin embargo, para un uso más intenso, aconsejaría la solución 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); ... } }


Otra solución simple y buena: mover la anotación @Cacheable a un nivel más bajo (DAO). El problema permanece en la clase DAO interna, pero resuelto para el servicio.


Use tejido estático para crear un proxy alrededor de su bean. En este caso, incluso los métodos "internos" funcionarían correctamente


Yo uso inner bean interno ( FactoryInternalCache ) con caché real para este propósito:

@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="https://.com/questions/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="https://.com/questions/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); } } }