Spring Cache @Cacheable - không hoạt động khi gọi từ một phương thức khác của cùng một bean


107

Bộ đệm mùa xuân không hoạt động khi gọi phương thức được lưu trong bộ nhớ cache từ một phương thức khác của cùng một bean.

Đây là một ví dụ để giải thích vấn đề của tôi một cách rõ ràng.

Cấu hình:

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

Dịch vụ lưu trong bộ nhớ 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);
        ...
    }

}

Kết quả :

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

Cuộc getEmployeeDatagọi phương thức sử dụng bộ nhớ cache employeeDatatrong cuộc gọi thứ hai như mong đợi. Nhưng khi getEmployeeDataphương thức được gọi trong AServicelớp (in getEmployeeEnrichedData), Cache sẽ không được sử dụng.

Đây có phải là cách bộ đệm mùa xuân hoạt động hay tôi đang thiếu thứ gì đó?


bạn có đang sử dụng cùng một giá trị cho someDatetham số không?
Dewfy

@Dewfy Có, nó giống nhau
Bala

Câu trả lời:


158

Tôi tin rằng đây là cách nó hoạt động. Từ những gì tôi nhớ đã đọc, có một lớp proxy được tạo ra để chặn tất cả các yêu cầu và phản hồi bằng giá trị được lưu trong bộ nhớ cache, nhưng các lệnh gọi 'nội bộ' trong cùng một lớp sẽ không nhận được giá trị được lưu trong bộ nhớ cache.

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

Chỉ các cuộc gọi phương thức bên ngoài đến thông qua proxy mới bị chặn. Điều này có nghĩa là thực tế, việc tự gọi một phương thức bên trong đối tượng đích gọi một phương thức khác của đối tượng đích, sẽ không dẫn đến việc chặn bộ nhớ cache thực sự trong thời gian chạy ngay cả khi phương thức được gọi được đánh dấu bằng @Cacheable.


1
Chà, nếu bạn thực hiện cuộc gọi thứ hai là Cacheable, nó sẽ chỉ bị lỡ một cache. Có nghĩa là, chỉ có lệnh gọi đầu tiên tới getEustingeeEnrichedData sẽ bỏ qua bộ nhớ cache. Lần gọi thứ hai tới nó sẽ sử dụng trả về được lưu trong bộ nhớ cache trước đó từ lần gọi đầu tiên để getEFasteeEnrichedData.
Shawn D.

1
@Bala Tôi có cùng một vấn đề, giải pháp của tôi là di chuyển @Cacheableđể DAO :( Nếu bạn có giải pháp tốt hơn xin vui lòng cho tôi biết, cảm ơn.
VAdaihiep

2
bạn cũng có thể viết một Dịch vụ, ví dụ như CacheService và đưa tất cả các phương thức vào bộ nhớ cache của bạn vào dịch vụ. Tự động phát hành Dịch vụ ở nơi bạn cần và gọi các phương thức. Đã giúp trong trường hợp của tôi.
DOUBL3P

Vì Spring 4.3, điều này có thể được giải quyết bằng cách sử dụng @Resourcetính năng tự động, hãy xem ví dụ stackoverflow.com/a/48867068/907576
radistao 19/02/18

1
Ngoài ra, @Cacheablephương thức bên ngoài cũng nên như vậy public, nó không hoạt động trên các phương thức gói-riêng. Tìm thấy nó một cách khó khăn.
anand

36

Kể từ Spring 4.3, vấn đề có thể được giải quyết bằng cách sử dụng tính năng tự động nạp vào @Resourcechú thích:

@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
Đã thử điều này 4.3.17và nó không hoạt động, các cuộc gọi selfkhông đi qua proxy và bộ nhớ cache (vẫn) bị bỏ qua.
Madbreaks

Đã làm cho tôi. Số lần truy cập vào bộ nhớ cache. Tôi sử dụng các phụ thuộc mùa xuân mới nhất kể từ ngày này.
Tomas Bisciak

Tôi có phải là người duy nhất nghĩ rằng điều này phá vỡ các mô hình, trông giống như một hỗn hợp singleton, v.v.?
2mia

tôi đã sử dụng phiên bản khởi động mùa xuân - 2.1.0.RELEASE và tôi gặp vấn đề tương tự. Giải pháp đặc biệt này hoạt động như một sự quyến rũ.
Deepan Prabhu Babu

18

Ví dụ dưới đây là những gì tôi sử dụng để truy cập proxy từ bên trong cùng một bean, nó tương tự như giải pháp của @ mario-eis, nhưng tôi thấy nó dễ đọc hơn một chút (có lẽ không phải :-). Dù sao, tôi muốn giữ các chú thích @Cacheable ở cấp dịch vụ:

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

Xem thêm Bắt đầu giao dịch mới trong Spring bean


1
Truy cập ngữ cảnh ứng dụng, ví dụ applicationContext.getBean(SettingService.class);, ngược lại với việc tiêm phụ thuộc. Tôi đề nghị tránh phong cách đó.
SingleShot

2
Có, nó sẽ tốt hơn để tránh nó, nhưng tôi không thấy một giải pháp tốt hơn cho vấn đề này.
molholm

10

Đây là những gì tôi làm cho các dự án nhỏ với việc chỉ sử dụng các lệnh gọi phương thức trong cùng một lớp. Tài liệu trong mã được khuyến khích mạnh mẽ, vì nó có thể gây khó khăn cho các đồng nghiệp. Nhưng nó dễ kiểm tra, đơn giản, nhanh chóng để đạt được và giúp tôi có được thiết bị AspectJ toàn diện. Tuy nhiên, để sử dụng nhiều hơn, tôi khuyên bạn nên sử dụng giải pháp 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
bạn có thể cho một ví dụ với AspectJ?
Sergio Bilello

Câu trả lời này là bản sao của stackoverflow.com/a/34090850/1371329 .
jaco0646

3

Trong Trường hợp của tôi, tôi thêm biến:

@Autowired
private AService  aService;

Vì vậy, tôi gọi getEmployeeDataphương thức bằng cách sử dụngaService

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

}

Nó sẽ sử dụng bộ nhớ cache trong trường hợp này.


2

Sử dụng dệt tĩnh để tạo proxy xung quanh bean của bạn. Trong trường hợp này, ngay cả các phương pháp 'nội bộ' sẽ hoạt động chính xác


"Dệt tĩnh" là gì? google không giúp được gì nhiều. Bất kỳ gợi ý để hiểu khái niệm này?
Bala

@Bala - chẳng hạn như trong dự án của chúng tôi, chúng tôi sử dụng <iajctrình biên dịch (từ ant) ​​để giải quyết tất cả các khía cạnh cần thiết cho các lớp có khả năng lưu trong bộ nhớ cache.
Dewfy

0

Tôi sử dụng bên trong bean bên trong ( FactoryInternalCache) với bộ nhớ cache thực cho mục đích này:

@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

giải pháp dễ nhất cho đến nay chỉ là tham khảo như sau:

AService.this.getEmployeeData(date);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.