Подтвердить что ты не робот

Spring Cache @Cacheable - не работает при вызове из другого метода того же bean

Spring кеш не работает при вызове кэшированного метода из другого метода того же bean.

Вот пример, чтобы объяснить мою проблему ясным образом.

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

<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 вызывается в классе AServicegetEmployeeEnrichedData), кеш не используется.

Является ли это тем, как работает кеш spring или я что-то не хватает?

4b9b3361

Ответ 1

Я считаю, что так оно и работает. Из того, что я помню, читается, создается класс прокси, который перехватывает все запросы и отвечает кешированным значением, но "внутренние" вызовы внутри одного класса не будут получать кешированное значение.

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

Только внешние вызовы методов, поступающие через прокси-сервер, перехвачены. Это означает, что самоисключение, по сути, метод в пределах целевого объекта, вызывающего другой метод целевого объекта, не приведет к фактическому перехвату кэша во время выполнения, даже если вызывается метод @Cacheable.

Ответ 2

Начиная с Spring 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);
    }
}

Ответ 3

Пример ниже - это то, что я использую для того, чтобы ударить прокси из одного и того же bean, он похож на решение @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);
}
...

См. также Запуск новой транзакции в Spring bean

Ответ 4

Вот что я делаю для небольших проектов с ограниченным использованием вызовов методов внутри одного класса. Внутрикодовая документация настоятельно рекомендуется, так как это может показаться неудачным для коллег. Но его легко проверить, просто, быстро достичь и избавит меня от полномасштабного инструментария 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);
        ...
    }
}

Ответ 5

Используйте статическое ткачество для создания прокси-сервера вокруг вашего bean. В этом случае даже "внутренние" методы будут работать правильно

Ответ 6

В моем случае я добавляю переменную:

@Autowired
private AService  aService;

Поэтому я getEmployeeData метод 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);
    ...
}

}

В этом случае он будет использовать кеш.

Ответ 7

Еще одно простое и правильное решение: переместите @Cacheable аннотацию на уровень ниже (DAO). Проблема остается внутри класса DAO, но решена для службы.

Ответ 8

Я использую внутренний внутренний 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 why
 * this internal bean is created: it "proxifies" overloaded {@code #createClient(...)} methods
 * to real AOP proxified cacheable bean method {@link #createClient}.
 *
 * @see <a href="#" onclick="location.href='https://stackoverflow.com/info/16899604/spring-cache-cacheable-not-working-while-calling-from-another-method-of-the-s'; return false;">Spring Cache @Cacheable - not working while calling from another method of the same bean</a>
 * @see <a href="#" onclick="location.href='https://stackoverflow.com/info/12115996/spring-cache-cacheable-method-ignored-when-called-from-within-the-same-class'; return false;">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);
    }
}
}