如何在不使用查询缓存的情况下缓存Spring Data JPA查询方法的结果?

时间:2014-10-07 18:14:51

标签: java hibernate spring-data ehcache spring-boot

我有一个带有Spring Data JPA(hibernate后端)存储库类的Spring Boot应用程序。我添加了一些自定义查找程序方法,其中一些方法具有特定的@Query注释,以告诉它如何获取数据。我已经为hibernate二级缓存设置了EhCache,但到目前为止,我可以获得这些结果缓存的唯一方法是启用hibernate查询缓存。我更喜欢定义一个特定的缓存并将实际的域对象存储在那里,就像它是一个普通的查找程序一样。以下是我的回购代码:

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

这是我定义的“promoteServiceXrefByCustomerId”缓存,它没有被使用:

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

我做错了什么?如果我启用StandardQueryCache,则此数据会缓存在那里,而hibernate不会执行查询。但是当我禁用查询缓存时,这不会被缓存。我在这做错了什么?请帮助!

3 个答案:

答案 0 :(得分:48)

您拥有的代码不起作用的原因是@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(…)的调用结果。请注意,我们在重写的@CacheEvict方法中添加了save(…),以便每当保存实体时,我们会使用查询方法填充的缓存被逐出。这可能也必须传播到delete(…)方法。

现在您可以继续配置专用CacheManager(有关详细信息,请参阅reference documentation)以插入您喜欢的任何缓存解决方案(在此处使用普通ConcurrentHashMap)。

 @Configuration
 @EnableCaching
 class CachingConfig {

   @Bean
   CacheManager cacheManager() {

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

     return cacheManager;
   }
 }

答案 1 :(得分:12)

您需要注意的是,放弃Hibernate QueryCache,您负责在保存,更新,删除影响查询结果的实体时使失效的查询失效(Oliver通过在保存时设置CacheEvict来做什么) - 我认为这可能是一种痛苦 - 或者至少你需要考虑并忽略它,如果它对你的场景不是真正的问题。

答案 2 :(得分:3)

首先,我引用您的问题:

  

我在做什么错?

您尝试为缓存命名 的方式与休眠将如何使用 不符。选中使用基于{org.hibernate.engine.spi.CacheInitiator的{​​{1}}

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并将缓存名称设置为@Cache将允许您使用 hibernate 使用ehcache缓存查询(此处不涉及spring缓存抽象)
  • 确定,设置硬编码的缓存名称不会令您满意,但至少您知道为什么会发生这种情况
  • 巴拉马奇·塞班(下面的帖子)非常正确

下面是我的一个项目中的配置,其中ehcache + @Query + @QueryHints 正常工作org.hibernate.cache.spi.UpdateTimestampsCache文件):

ehcache/ehcache-in-memory.xml

和hibernate.properties:

<?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>

和适用于我的解释的pom.xml中的某些版本:

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

完整的工作测试是在以下位置找到的 image.persistence.repositories.ImageRepositoryTest.java https://github.com/adrhc/photos-server/tree/how-to-cache-results-of-a-spring-data-jpa-query-method-without-using-query-cache
是的,如果您确实想使用我的shell脚本,请运行<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> 或更改我的mvn clean install。然后代表3x env.sh调用检查sql查询的数量。