【问题标题】:Hibernate Second level query caching issue with same where clause具有相同 where 子句的休眠二级查询缓存问题
【发布时间】:2013-02-02 19:17:23
【问题描述】:

我的应用程序使用 JPA (1.2)、Spring (3.1.2)、Spring Data (1.1.0) 和 Hibernate (4.1.7)。 数据库:Oracle10g

我们已启用二级缓存。它在实体上运行良好,但在命名查询缓存上产生了问题。

问题是:如果命名查询具有相同的 where 子句但不同的 select 语句,那么无论执行第一个查询,它都会为第二个查询提供相同的结果。

就像我的第一个查询(countRelease)是

select count(r) from Release r where r.type in 
(select c.contentTypeId from ContentType c where c.parentContentTypeId is NULL)
order by r.validityStart

第二个查询(findRelease)是

select r from Release r where r.type in 
(select c.contentTypeId from ContentType c where c.parentContentTypeId is NULL)   
order by r.validityStart

如果首先运行第一个查询,那么 count 会出现,然后如果我运行第二个查询,那么 count 也会出现,它应该给我发布实体的列表。

如果我删除查询缓存,它可以正常工作,如果我在第二个查询 where 子句中进行一些更改,那么它也可以正常工作,但我不需要这样做。

我们如何解决这个问题?

我的 Java 代码

@Query(name="findRelease")
@QueryHints({@QueryHint(name = "org.hibernate.cacheRegion", value ="cvodrelease"),@QueryHint(name = "org.hibernate.cacheable", value ="true") })
public List<Release> findRelease();

@Query(name="countRelease")
@QueryHints({@QueryHint(name = "org.hibernate.cacheRegion", value ="cvodrelease"),@QueryHint(name = "org.hibernate.cacheable", value ="true") })
public Long  countOfRelease(Date today);

缓存配置

<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
<property name="hibernate.cache.use_query_cache" value="true"/>
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.provider_class" value="net.sf.ehcache.hibernate.EhCacheProvider" /> 
<property name="hibernate.cache.provider_configuration_file_resource_path" value="ehcache.xml" />

<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"  p:cacheManager-ref="ehcache"/>

<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:configLocation="ehcache.xml"  p:shared="true"/> 

【问题讨论】:

  • 有没有人有解决上述问题的办法
  • 有没有人有解决上述问题的方法...或其错误
  • QueryKey 类似乎存储了查询字符串,所以这确实很奇怪。我会在QueryKey.generateQueryKey() 上设置一个断点,并尝试找出为什么它的queryString 参数对于两个不同的查询是相同的。
  • @Ranu Jain 最好把 Java 代码贴出来供审阅。代码说明了一切。我所知。二级缓存不会影响查询结果。这是另一个问题。
  • 没有 java 代码,因为它只有 @namedQuery 的方法名称,因为我们使用的是 JPA 存储库

标签: java hibernate jpa spring-data spring-data-jpa


【解决方案1】:

查询缓存维护查询与参数组合构成键和值作为标识符的结果。

来自文档:

  • 当数据缓存中没有查询结果中某个id的缓存数据时,不使用缓存。

  • 不会缓存导致自定义字段类型或 BigDecimal 或 BigInteger 字段投影的查询。

  • 请注意,查询缓存不会缓存结果集中实际实体的状态; 它只缓存标识符值和值类型的结果。查询缓存应始终与二级缓存结合使用。

最好获取整个对象,而不是查询中的字段。

可能它忽略了查询的选择部分并缓存了结果。 后面的部分对于两个查询都是相同的,因此产生相同的结果。您可以尝试更改查询执行顺序并观察结果。

【讨论】:

  • 我没有获取单个字段。查询中提到的第一个查询是获取计数,第二个查询是获取实体对象。 Hense 值得我无法组合查询....请为我提供解决方案...
  • @RanuJain 对于单个字段,我的意思是不获取整个对象,这是计数。我不明白你为什么需要计数查询。您可以执行第二个查询并可以从其结果中获取列表大小作为计数。
【解决方案2】:

JPA 1.0 标准不包含缓存(并且 JPA 1.2 不存在)。

JPA 2.0 标准引入了缓存 - 包括共享缓存(每个 EntityManagerFactory 实例的“一级缓存”)和应用程序缓存(所有 EntityManagerFactor 实例的二级缓存)。此外,每个 EntityManager 实例的每个 PersistenceContext 都充当其自己的最低级别缓存 - “零级缓存”。

这意味着您的行为都是特定于 Hibernate 4.1.7 的,与任何标准或任何其他产品无关。

当数据缓存没有缓存时不使用缓存 查询结果中 id 的数据。

这是 Apache OpenJPA 文档的直接引用,而不是 Hibernate 或 JPA 规范。您可以忽略,但对于 Hibernate 来说似乎是这样。

不会缓存导致自定义字段类型或 BigDecimal 或 BigInteger 字段投影的查询。

这是来自 Oracle Kodo JPA 文档的直接引用,而不是 Hibernate 或 JPA 规范。忽略这一点可能是明智的。

查询缓存不会缓存缓存中实际实体的状态。它缓存标识符值和值类型的结果。因此,对于那些应作为查询结果缓存的一部分缓存的实体,始终将查询缓存与二级缓存结合使用。 .

这是从 Hibernate 4.1 文档中直接引用的。所以你可以遵循这个建议——只要你把它放在上下文中:如果你想缓存从查询返回的实体,它就是说包括二级缓存。如果您不想缓存整个实体对象,而只想缓存包含原始数据类型(投影)的 NamedQueries 的结果,那么您只需要一级缓存。

我的建议:

  1. 我认为问题可能是 COUNT(r) 返回一个 BigInteger 给 java,它不能转换为 Object 进行缓存。您可以在查询上调用 addScalar("count", Hibernate.LONG) 来告诉 hibernate 使用不同的类型 - LONG。见blog.pfa-labs.com/2009/12/caching-raw-sql-count-with-hibernate.htmlIs/Can Hibernate's Second-Level Cache be Used for COUNT() operations?

  2. 查询缓存应该能够处理这个问题。只有实体对象才需要二级缓存。

  3. 要非常小心地了解您尝试缓存的对象的读/写行为 - 并确保读取次数远大于写入次数。否则缓存可能没有任何好处,甚至会减慢速度并导致数据不一致。

  4. 请注意,某些 JDBC 驱动程序也会缓存数据 - 如果是您的,它会影响 JPA 结果,而 JPA 甚至不会知道它。

来自 Mike Keith 的“Pro JPA 2”: 大多数 [JDBC] 驱动程序缓存连接和语句。一些缓存还跟踪表或列的状态,这对 JPA 提供者本质上是透明的,但是在不必去数据库获取每次调用的数据方面仍然可以提供一些节省。仅当已知数据是只读的或驱动程序以独占方式控制数据库访问时,这通常在驱动程序中是可行的。

JDBC 缓存(如果可用)应该可以通过特定于驱动程序的配置设置进行控制。

编辑:

在第一个查询中,“order by r.validityStart”什么都不做 - 你可以删除它,一切都会正常工作。

【讨论】:

  • JPA 1.2 不存在。 JPA 1.0 会,JPA 2.0 会,JPA2.1 会
  • 谢谢。更正版本 JPA 1.2 -> 1.0。
  • 感谢您的回答,但仍然无法解决我的问题。我的计数查询只给我 Long 的结果,但计数查询和对象选择查询具有相同的 where 条件,所以它总是给出首先运行的结果,并且它为其他查询抛出异常。
  • 很高兴,看看我能不能做得更多。如果您的代码中没有错误(无法确认这一点,因为您只包含 JPQL 字符串且没有 Java 查询调用代码),那么 Hibernate/选定的缓存提供程序中必须存在错误 - 无论您如何配置缓存,它们永远不应该返回非法结果 - 永远是一个错误。我的建议:(1)包括您的完整代码 - 它不会有任何伤害,它可以帮助我们重现您的问题并找到错误(2)让我们知道您正在使用的数据库 - MySQL?甲骨文? (3) 让我们知道您使用的是什么缓存提供程序 - hibernate 的内置缓存?
  • (4) 当您查询数据库以查看已编译/缓存的查询计划时会发生什么?例如。在 Oracle SELECT * FROM V$SQL WHERE SQL_TEXT LIKE '%select c.contentTypeId from ContentType c where c.parentContentTypeId is NULL%'; (5)当你改变订单查询被执行时会发生什么?
【解决方案3】:

我相信您的问题与二级缓存无关 - 这是其他问题。缓存本身不能改变预期的结果。

为了更加确定,您可以尝试以下代码在开始第二次查询之前清除二级缓存:

session.setCacheMode(CacheMode.IGNORE); // session here is the SessionFactory

如果问题仍然存在,那么很明显二级缓存不是罪魁祸首。

【讨论】:

  • 如果没有二级缓存,当我们启用二级缓存时一切正常,然后只发生此问题。因此,仅在二级缓存中值得做错事
  • @RanuJain:可能是它,或者可能是二级缓存意外触发的东西。我的意思是二级缓存不是你问题的直接罪魁祸首。
猜你喜欢
  • 1970-01-01
  • 2015-10-23
  • 1970-01-01
  • 1970-01-01
  • 2010-10-20
  • 1970-01-01
  • 2012-04-30
  • 2016-09-12
相关资源
最近更新 更多