web-dev-qa-db-ja.com

クエリキャッシュを使用せずにSpring Data JPAクエリメソッドの結果をキャッシュする方法

Spring Data JPA(hibernateバックエンド)リポジトリクラスを使用したSpring Bootアプリがあります。いくつかのカスタムFinderメソッドを追加しました。特定の@Query注釈を使用して、データの取得方法を指定します。すでに休止状態の2次キャッシュ用にEhCacheをセットアップしましたが、これまでのところ、これらの結果のキャッシュを取得できる唯一の方法は、休止状態のクエリキャッシュを有効にすることです。特定のキャッシュを定義し、実際のドメインオブジェクトを通常のFinderのようにそこに保存したいと思います。以下は私のレポコードです:

public interface PromotionServiceXrefRepository extends PagingAndSortingRepository<PromotionServiceXref, Integer> {

  @Query("SELECT psx FROM Customer c " +
         "JOIN c.customerProductPromotions cpp " +
         "JOIN cpp.productPromotion pp " +
         "JOIN pp.promotion p JOIN p.promotionServiceXrefs psx " +
         "WHERE c.customerId = ?1")
  @QueryHints(@QueryHint(name = "org.hibernate.cacheable", value = "true"))
  @Cache(usage = CacheConcurrencyStrategy.READ_ONLY, region = "promotionServiceXrefByCustomerId")
  Set<PromotionServiceXref> findByCustomerId(int customerId);
}

そして、ここに私が定義した「promotionServiceXrefByCustomerId」キャッシュがありますが、これは使用されていません。

<cache name="promotionServiceXrefByCustomerId" overflowToDisk="true" diskPersistent="true"
       maxEntriesLocalHeap="3000000" eternal="true" diskSpoolBufferSizeMB="20" memoryStoreEvictionPolicy="LFU"
       transactionalMode="off" statistics="true">
</cache>

私は何を間違えていますか? StandardQueryCacheを有効にすると、このデータはそこでキャッシュされ、hibernateはクエリを実行しません。ただし、クエリのキャッシュを無効にすると、キャッシュされません。ここで何が間違っていますか?助けてください!

18
Kevin M

使用しているコードが機能しないのは、_@Cache_がそのように機能することを意図していないためです。クエリメソッドの実行結果をキャッシュする場合、最も簡単な方法は、Springの caching abstraction を使用することです。

_interface PromotionServiceXrefRepository extends PagingAndSortingRepository<PromotionServiceXref, Integer> {

  @Query("…")
  @Cacheable("servicesByCustomerId")
  Set<PromotionServiceXref> findByCustomerId(int customerId);

  @Override
  @CacheEvict(value = "servicesByCustomerId", key = "#p0.customer.id")
  <S extends PromotionServiceXref> S save(S service);
}
_

この設定により、findByCustomerId(…)への呼び出しの結果が顧客識別子によってキャッシュされます。オーバーライドされたsave(…)メソッドに_@CacheEvict_を追加したことに注意してください。これにより、エンティティが保存されるたびに、クエリメソッドで生成されたキャッシュが削除されます。これはおそらくdelete(…)メソッドにも伝播する必要があります。

専用のCacheManager(詳細は 参照ドキュメント を参照)を構成して、好みのキャッシングソリューションをプラグインすることができます(ここではプレーンConcurrentHashMapを使用) 。

_ @Configuration
 @EnableCaching
 class CachingConfig {

   @Bean
   CacheManager cacheManager() {

     SimpleCacheManager cacheManager = new SimpleCacheManager();
     cacheManager.addCaches(Arrays.asList(new ConcurrentMapCache("servicesByCustomerId)));

     return cacheManager;
   }
 }
_
53
Oliver Drotbohm

Hibernate QueryCacheを放棄することにより、クエリ結果に影響を与えるエンティティを保存、更新、削除するときに失効するクエリを無効にする責任があることに注意する必要があります(オリバーが保存時にCacheEvictを設定することで行うこと)苦痛になる可能性があります。少なくとも、シナリオに問題がない場合は、考慮して無視する必要があります。

13
Balamaci Serban

最初にあなたの質問を引用します:

私は何を間違えていますか?

あなたがしようとしている方法はnameキャッシュは適切ではない hibernateがキャッシュを使用する方法です。以下に基づく_org.hibernate.engine.spi.CacheInitiator_を使用する_org.hibernate.internal.CacheImpl_を確認します。

_if ( settings.isQueryCacheEnabled() ) {
    final TimestampsRegion timestampsRegion = regionFactory.buildTimestampsRegion(
            qualifyRegionName( UpdateTimestampsCache.REGION_NAME ),
            sessionFactory.getProperties()
    );
    updateTimestampsCache = new UpdateTimestampsCache( sessionFactory, timestampsRegion );
    ...
}
_

また、_UpdateTimestampsCache.REGION_NAME_(_org.hibernate.cache.spi.UpdateTimestampsCache_と等しい)は、キャッシュ名として欠落しているものです。 クエリキャッシュには、exactlyそのキャッシュ名を使用する必要があります。

問題に関連する他のいくつかの考え:

  • _@Cache_を削除し、キャッシュ名を_org.hibernate.cache.spi.UpdateTimestampsCache_に設定すると、クエリがhibernateによってehcacheでキャッシュできるようになります(ここではスプリングキャッシュの抽象化は関係ありません)
  • ハードコードされたキャッシュ名を設定しても満足することはありませんが、少なくともこれが起こる理由は知っています
  • Balamaci Serban(すぐ下の投稿)は痛々しいほど正しい

以下は、ehcache + @Query + @QueryHintsが期待通りに動作する私のプロジェクトの1つからの構成です(_ehcache/ehcache-in-memory.xml_ file):

_<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="in-memory" xmlns="http://ehcache.org/ehcache.xsd">
    <!--<diskStore path="Java.io.tmpdir"/>-->

    <!--
        30d = 3600×24×30 = 2592000
    -->

    <cache name="org.hibernate.cache.internal.StandardQueryCache"
           maxElementsInMemory="9999" eternal="false"
           timeToIdleSeconds="2592000" timeToLiveSeconds="2592000"
           overflowToDisk="false" overflowToOffHeap="false"/>

    <cache name="org.hibernate.cache.spi.UpdateTimestampsCache"
           maxElementsInMemory="9999" eternal="true"
           overflowToDisk="false" overflowToOffHeap="false"/>

    <defaultCache maxElementsInMemory="9999" eternal="false"
                  timeToIdleSeconds="2592000" timeToLiveSeconds="2592000"
                  overflowToDisk="false" overflowToOffHeap="false"/>
</ehcache>
_

およびhibernate.properties:

_hibernate.jdbc.batch_size=20
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.validator.autoregister_listeners=false
hibernate.cache.use_second_level_cache=true
hibernate.cache.use_query_cache=true
hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
hibernate.hbm2ddl.auto=update
net.sf.ehcache.configurationResourceName=ehcache/ehcache-in-memory.xml
hibernate.dialect=org.hibernate.dialect.H2Dialect
_

そして、私の説明が適用されるpom.xmlからのいくつかのバージョン:

_<springframework.version>5.0.6.RELEASE</springframework.version>
<spring-security.version>5.0.5.RELEASE</spring-security.version>
<spring-data-jpa.version>2.1.0.RELEASE</spring-data-jpa.version>
<hibernate.version>5.2.13.Final</hibernate.version>
<jackson-datatype-hibernate5.version>2.9.4</jackson-datatype-hibernate5.version>
_

そして、完全な動作テストはimage.persistence.repositories.ImageRepositoryTest.Javaにあります: https://github.com/adrhc/photos-server/tree/how-to-cache -spring-data-jpa-query-method-without-using-query-cacheの結果
はい、本当にシェルスクリプトを使用する場合は、_mvn clean install_を実行するか、_env.sh_を変更します。 3x imageRepository.count()呼び出しに代わってSQLクエリの数を確認します。

3
adrhc