休眠二级查询缓存问题,具有相同的 where 子句

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

我们启用了二级缓存。它对实体工作正常,但它在命名查询缓存上产生了问题。

问题是:如果命名查询具有相同的 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

和第二个查询(查找发布)是

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

如果首先运行第一个查询,那么计数将来临,之后如果我运行第二个查询,那么计数也会来,它应该给我发布实体的列表。

如果我删除查询缓存,它工作正常,如果我在第二个查询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"/> 

答案 1

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,而Java不能将其转换为Object进行缓存。您可以在查询上调用addScalar(“count”,Hibernate.LONG)来告诉hibernate使用不同的类型 - LONG。请参阅 blog.pfa-labs.com/2009/12/caching-raw-sql-count-with-hibernate.html 以及是否/是否可以将休眠的第二级缓存用于 COUNT() 操作?

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

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

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

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

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

编辑:

在第一个查询中,“按 r.validityStart 排序”不执行任何操作 - 您可以删除它,一切都会起作用。


答案 2

查询缓存维护结果,其中查询与参数组合在一起构成了键和值作为标识符。

从文档中:

  • 当数据缓存在查询结果中没有 ID 的任何缓存数据时,不使用缓存。

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

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

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

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


推荐