Spring Cache @Cacheable-同じBeanの別のメソッドから呼び出しているときに機能しない


107

同じBeanの別のメソッドからキャッシュされたメソッドを呼び出すと、Springキャッシュが機能しません。

ここに私の問題を明確に説明する例があります。

構成:

<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予想通り2回目の呼び出しで。ただし、getEmployeeDataメソッドがAServiceクラス内(内getEmployeeEnrichedData)で呼び出されると、キャッシュは使用されません。

これは春のキャッシュの仕組みですか、それとも何か不足していますか?


someDateパラメータに同じ値を使用していますか?
Dewfyは2013年

@Dewfyはい、同じです
Bala

回答:


158

私はこれがどのように機能するかを信じています。私が読んだことを思い出すと、すべてのリクエストをインターセプトしてキャッシュされた値で応答するプロキシクラスが生成されますが、同じクラス内の「内部」呼び出しはキャッシュされた値を取得しません。

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

代行受信されるのは、プロキシ経由で着信する外部メソッド呼び出しのみです。つまり、実際には、ターゲットオブジェクトの別のメソッドを呼び出すターゲットオブジェクト内のメソッドを自己呼び出ししても、呼び出されたメソッドが@Cacheableでマークされていても、実行時に実際のキャッシュインターセプトは発生しません。


1
まあ、もし2番目の呼び出しをCacheableにすると、キャッシュミスは1つだけになります。つまり、getEmployeeEnrichedDataへの最初の呼び出しだけがキャッシュをバイパスします。2回目の呼び出しでは、getEmployeeEnrichedDataへの最初の呼び出しから以前にキャッシュされた戻り値を使用します。
Shawn D.

1
@Bala私は同じ問題を持って、私の解決策は動きです@CacheableDAOへ:(あなたがよりよい解決策を持っている場合は私に知らせてください、感謝を。
VAdaihiep

2
CacheServiceなどのサービスを記述して、すべてのキャッシュメソッドをサービスに配置することもできます。必要な場所にサービスを自動配線し、メソッドを呼び出します。私の場合に役立ちました。
DOUBL3P 2017

Spring 4.3以降、これは@Resource自己自動
配線

1
また、外部@Cacheableメソッドはでなければなりpublicません。パッケージプライベートメソッドでは機能しません。それは難しい方法でした。
アナンド

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.RELEASEを使用しましたが、同じ問題が発生しました。この特定のソリューションは魅力のように機能しました。
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 2017年

2
はい、それは避けた方が良いでしょうが、私はこの問題のより良い解決策を見ていません。
molholm 2017年

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の例を挙げていただけますか?
セルジオビレッロ2016年

この回答は、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

静的ウィービングを使用して、Beanの周囲にプロキシを作成します。この場合、「内部」メソッドでも正しく機能します


「スタティックウィービング」とは?グーグルはあまり役に立ちません。この概念を理解するための指針はありますか?
Bala

@Bala-ちょうど私たちのプロジェクトの例では、<iajcキャッシュ可能なクラスのすべての必要な側面を解決する(antからの)コンパイラーを使用します。
Dewfy 2013年

0

FactoryInternalCacheこの目的のために、実際のキャッシュで内部インナーBean()を使用します。

@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);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.