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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26242492/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 02:04:03  来源:igfitidea点击:

How to cache results of a Spring Data JPA query method without using query cache?

javahibernatespring-dataehcachespring-boot

提问by Kevin M

I have a Spring Boot app with Spring Data JPA (hibernate backend) repository classes. I've added a couple custom finder methods, some with specific @Queryannotation to tell it how to get the data. I have already set up EhCache for the hibernate 2nd level cache, but so far, the only way I can get these results caching is to enable the hibernate query cache. I'd prefer to define a specific cache and store the actual domain objects there just as if it were a normal finder. Below is my repo code:

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

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

And here is the "promotionServiceXrefByCustomerId" cache I defined, that is NOT being used:

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

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

What am I doing wrong? If I enable StandardQueryCachethen this data gets cached there and hibernate does not execute a query. But when I disable the query caching, this does not get cached. What am I doing wrong here? PLEASE HELP!

我究竟做错了什么?如果我启用,StandardQueryCache那么这些数据会被缓存在那里,并且休眠不会执行查询。但是当我禁用查询缓存时,这不会被缓存。我在这里做错了什么?请帮忙!

采纳答案by Oliver Drotbohm

The reason the code you have is not working is that @Cacheis not intended to work that way. If you want to cache the results of a query method execution, the easiest way is to use Spring's caching abstraction.

您拥有的代码不起作用的原因是它@Cache不打算以这种方式工作。如果要缓存查询方法执行的结果,最简单的方法是使用 Spring 的缓存抽象

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

This setup will cause results of calls to findByCustomerId(…)be cached by the customer identifier. Note, that we added an @CacheEvictto the overridden save(…)method, so that the cache we populate with the query method is evicted, whenever an entity is saved. This probably has to be propagated to the delete(…)methods as well.

此设置将导致调用结果findByCustomerId(…)由客户标识符缓存。请注意,我们@CacheEvict在重写的save(…)方法中添加了,以便在保存实体时驱逐我们使用查询方法填充的缓存。这可能也必须传播到delete(…)方法。

Now you can go ahead an configure a dedicated CacheManager(see the reference documentationfor details) to plug in whichever caching solution you prefer (using a plain ConcurrentHashMaphere).

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

 @Configuration
 @EnableCaching
 class CachingConfig {

   @Bean
   CacheManager cacheManager() {

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

     return cacheManager;
   }
 }

回答by Balamaci Serban

You need to be aware that by giving up on the Hibernate QueryCache your are responsible for invalidating the queries that become stale when saving, updating, deleting entities that influenced the query result(what Oliver is doing by setting CacheEvict on save) - which I think can be a pain- or at least you need to take into account and ignore it if it's not really a problem for your scenario.

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

回答by adrhc

First I quote your question:

首先我引用你的问题:

What am I doing wrong?

我究竟做错了什么?

The way you are trying to namethe cache is not appropriateto how hibernate will use it. Check org.hibernate.engine.spi.CacheInitiatorwhich uses org.hibernate.internal.CacheImplwhich is based on:

您尝试命名缓存的方式不适合休眠将如何使用它。检查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 );
    ...
}

And UpdateTimestampsCache.REGION_NAME(equals to org.hibernate.cache.spi.UpdateTimestampsCache) is what you are missing as the cache name. For the query cacheyou'll have to use exactlythat cache name and no other!

并且UpdateTimestampsCache.REGION_NAME(等于org.hibernate.cache.spi.UpdateTimestampsCache) 是您缺少的缓存名称。对于查询缓存,您必须完全使用该缓存名称,而不能使用其他名称!

Now few other thoughts related to your problem:

现在与您的问题相关的其他想法很少:

  • removing @Cacheand setting cache name to org.hibernate.cache.spi.UpdateTimestampsCachewill allow your query to be cached with ehcache by hibernate(spring cache abstraction is not involved here)
  • setting a hardcoded cache name won't make you happy I'm sure but at least you know why this happens
  • Balamaci Serban (the post just below) is painfully right
  • 删除@Cache和设置缓存名称org.hibernate.cache.spi.UpdateTimestampsCache将允许您的查询通过hibernate使用 ehcache 进行缓存(此处不涉及 spring 缓存抽象)
  • 设置一个硬编码的缓存名称不会让你高兴我敢肯定,但至少你知道为什么会发生这种情况
  • Balamaci Serban(下面的帖子)非常正确

Below is the configuration from one of my projects where ehcache + @Query + @QueryHints works as expected(ehcache/ehcache-in-memory.xmlfile):

以下是我的一个项目的配置,其中 ehcache + @Query + @QueryHints按预期工作ehcache/ehcache-in-memory.xml文件):

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

and hibernate.properties:

和 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

and some versions from pom.xml for which my explanation applies:

以及我的解释适用的 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>
<Hymanson-datatype-hibernate5.version>2.9.4</Hymanson-datatype-hibernate5.version>

And the full working test is image.persistence.repositories.ImageRepositoryTest.javafound here: https://github.com/adrhc/photos-server/tree/how-to-cache-results-of-a-spring-data-jpa-query-method-without-using-query-cache
Yep, run mvn clean installor change my env.shif you really want to use my shell scripts. Check then the number of sql queries on behalf of 3x imageRepository.count()call.

完整的工作测试是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 脚本,请运行mvn clean install或更改我的env.sh。然后检查代表 3ximageRepository.count()调用的 sql 查询次数。