Spring Cache @Cacheable - не працює під час виклику з іншого методу того ж боба


107

Веб-кеш не працює при виклику кешованого методу з іншого методу того ж біна.

Ось приклад, щоб пояснити мою проблему чітко.

Конфігурація:

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

Кешована послуга:

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

}

Результат:

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

В getEmployeeDataвикористовує виклик методу кешування employeeDataпри другому виклику , як очікувалося. Але коли getEmployeeDataметод викликається в AServiceкласі (in getEmployeeEnrichedData), кеш не використовується.

Це як працює весняний кеш, чи я щось пропускаю?


Ви використовуєте те ж значення для someDateпарам?
роси

@Dewfy Так, це те саме
Бала

Відповіді:


158

Я вважаю, що так це працює. З того, що я пам'ятаю, як читав, існує проксі-клас, який перехоплює всі запити та відповідає з кешованим значенням, але "внутрішні" дзвінки в межах одного класу не отримають кешоване значення.

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

Перехоплюються лише зовнішні виклики методів, що надходять через проксі. Це означає, що самонавіювання, фактично, метод всередині цільового об'єкта, що викликає інший метод цільового об'єкта, не призведе до фактичного перехоплення кешу під час виконання, навіть якщо викликаний метод позначений символом @Cacheable.


1
Що ж, якщо ви також зробите кеш-пам'ять другого виклику, він буде мати лише один пропуск кеша. Тобто, лише перший виклик getE EmployeeEnrichedData обійде кеш. Другий виклик до нього використовував би попередньо кешоване повернення з першого дзвінка для отриманняEeuroeeEnrichedData.
Шон Д.

1
@Bala У мене те саме питання, моє рішення перейти @Cacheableдо DAO :( Якщо у вас є краще рішення, будь ласка, повідомте мене, дякую.
VAdaihiep

2
Ви також можете написати Сервіс, наприклад, CacheService і ввести всі свої методи кешування в службу. Автомобільно прокладіть Службу, де вам потрібно, і зателефонуйте до методів. Допоміг у моєму випадку.
DOUBL3P

Так як Spring 4.3 це може бути вирішена з допомогою @Resourceвласного автоматичного зв'язування, см приклад stackoverflow.com/a/48867068/907576
radistao

1
Також зовнішній @Cacheableметод повинен бути public, він не працює на пакетних приватних методах. Знайшов це важкий шлях.
anand

36

Починаючи з весни 4.3, проблему можна було вирішити за допомогою самоавтоматичного підключення через @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.17і це не спрацювало, дзвінки selfне проходити через проксі, і кеш (все-таки) обійшов.
Madbreaks

Працювали для мене. Кеш-хіти. Я використовую останні весняні залежності від цієї дати.
Томаш Бісьяк

Я єдиний, хто думає, що це порушує схеми, схоже на суміш синглів, тощо тощо?
2mia

Я використовував версію для запуску весняного завантаження - 2.1.0. РЕЛІЗ, і у мене був той самий випуск. Це конкретне рішення спрацювало як шарм.
Deepan Prabhu Babu

18

Наведений нижче приклад - це те, що я використовую для потрапляння на проксі з одного і того ж боба, це схоже на рішення @ mario-eis, але я вважаю його трохи читабельнішим (можливо, це не так :-). У будь-якому випадку, мені подобається зберігати анотації @Cacheable на рівні обслуговування:

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

Дивіться також Запуск нової транзакції у весняному бобі


1
Доступ до контексту програми, наприклад applicationContext.getBean(SettingService.class);, є протилежною ін'єкції залежності. Я пропоную уникати цього стилю.
SingleShot

2
Так, краще було б цього уникнути, але я не бачу кращого рішення цієї проблеми.
molholm

10

Ось що я роблю для малих проектів із лише граничним використанням викликів методів у межах одного класу. Документація щодо коду настійно не рекомендується, оскільки це може здатися нелегким для колег. Але його легко перевірити, простий, швидкий для досягнення і пошкодує мене повноцінний прилад AspectJ. Однак для більш важкого використання я б порадив рішення 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
ви можете навести приклад з AspectJ?
Серхіо Білелло

Ця відповідь є дублікатом stackoverflow.com/a/34090850/1371329 .
jaco0646

3

У своєму випадку я додаю змінну:

@Autowired
private AService  aService;

Тому я називаю getEmployeeDataметод за допомогоюaService

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

}

У цьому випадку він буде використовувати кеш.


2

Використовуйте статичне плетіння, щоб створити проксі навколо вашої квасолі. У цьому випадку навіть "внутрішні" методи спрацювали б коректно


Що таке "статичне плетіння"? Google не дуже допомагає. Будь-які вказівники розуміють ці поняття?
Бала

@Bala - саме для нашого проекту ми використовуємо <iajcкомпілятор (від мурашника), який вирішує всі аспекти необхідності для кеш-класів.
роси

0

Для цього я використовую внутрішній внутрішній bean ( FactoryInternalCache) з справжнім кешем:

@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

Найпростіше рішення на сьогоднішній день - це просто посилання:

AService.this.getEmployeeData(date);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.