Spring Cache @Cacheable - ไม่ทำงานขณะโทรจากวิธีอื่นของ bean เดียวกัน


108

Spring cache ไม่ทำงานเมื่อเรียกวิธีการแคชจากวิธีอื่นของ 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การเรียกใช้เมธอดภายในAServiceคลาส (ในgetEmployeeEnrichedData) แคชจะไม่ถูกใช้

สปริงแคชทำงานอย่างไรหรือฉันพลาดอะไรไป


คุณใช้ค่าsomeDateพารามิเตอร์เดียวกันหรือไม่
Dewfy

@Dewfy ใช่เหมือนกัน
บาลา

คำตอบ:


160

ฉันเชื่อว่านี่คือวิธีการทำงาน จากสิ่งที่ฉันจำได้ว่าอ่านมีคลาสพร็อกซีที่สร้างขึ้นเพื่อสกัดกั้นคำขอทั้งหมดและตอบสนองด้วยค่าที่แคชไว้ แต่การเรียก "ภายใน" ภายในคลาสเดียวกันจะไม่ได้รับค่าแคช

จากhttps://code.google.com/p/ehcache-spring-annotations/wiki/UsingCacheable

เฉพาะการเรียกวิธีภายนอกที่เข้ามาผ่านพร็อกซีเท่านั้นที่ถูกดักฟัง ซึ่งหมายความว่าวิธีการเรียกใช้ด้วยตนเองมีผลบังคับใช้วิธีการภายในวัตถุเป้าหมายที่เรียกวิธีอื่นของวัตถุเป้าหมายจะไม่นำไปสู่การสกัดกั้นแคชจริงที่รันไทม์แม้ว่าเมธอดที่เรียกใช้จะถูกทำเครื่องหมายด้วย @Cacheable


1
ถ้าคุณโทรครั้งที่สอง Cacheable ด้วยมันจะมีแคชพลาดเพียงครั้งเดียว นั่นคือมีเพียงการเรียก getEmployeeEnrichedData ครั้งแรกเท่านั้นที่จะข้ามแคช การเรียกครั้งที่สองจะใช้ผลตอบแทนที่แคชไว้ก่อนหน้านี้จากการเรียกครั้งแรกไปยัง getEmployeeEnrichedData
Shawn D.

1
@ บาลาฉันมีปัญหาเดียวกันวิธีแก้ปัญหาของฉันคือย้าย@Cacheableไปที่ DAO :( หากคุณมีวิธีแก้ไขที่ดีกว่าโปรดแจ้งให้เราทราบขอบคุณ
VAdaihiep

2
คุณยังสามารถเขียนบริการเช่น CacheService และใส่วิธีการแคชทั้งหมดของคุณลงในบริการ เรียกใช้บริการอัตโนมัติตามที่คุณต้องการและโทรหาวิธีการ ช่วยในกรณีของฉัน
DOUBL3P

ตั้งแต่ Spring 4.3 สิ่งนี้สามารถแก้ไขได้โดยใช้การ@Resource
เดินสายอัตโนมัติ

1
นอกจากนี้@Cacheableควรใช้วิธีภายนอกด้วยวิธีpublicนี้ใช้ไม่ได้กับวิธีการส่วนตัวแบบแพ็กเกจ พบว่ามันเป็นวิธีที่ยาก
anand

36

ตั้งแต่ 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);
    }
}

2
ลองใช้4.3.17แล้วไม่ได้ผลโทรไปselfไม่ผ่านพร็อกซีและแคชถูกข้าม (ยัง) อยู่
Madbreaks

ทำงานให้ฉัน แคชฮิต ฉันใช้การอ้างอิงสปริงล่าสุด ณ วันที่นี้
Tomas Bisciak

ฉันเป็นคนเดียวที่คิดว่านี่เป็นการแบ่งรูปแบบดูเหมือนการผสมผสานแบบซิงเกิลตัน ฯลฯ หรือไม่?
2mia

ฉันใช้สปริงบูตรุ่นเริ่มต้น - 2.1.0 รีลีสและฉันมีปัญหาเดียวกัน โซลูชันเฉพาะนี้ทำงานได้อย่างมีเสน่ห์
Deepan Prabhu Babu

18

ตัวอย่างด้านล่างคือสิ่งที่ฉันใช้เพื่อตีพร็อกซีจากภายใน 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


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 ได้ไหม
Sergio Bilello

คำตอบนี้เป็นซ้ำ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

@Bala - ตัวอย่างเช่นในโครงการของเราเราใช้<iajcคอมไพเลอร์ (จากมด) ที่แก้ไขความจำเป็นทั้งหมดสำหรับคลาสที่สามารถแคชได้
Dewfy

0

ฉันใช้ inner 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);
    }
}
}

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.