14

我的应用程序使用 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"/> 
4

3 回答 3

3

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.html 以及 是否/可以将 Hibernate 的二级缓存用于 COUNT() 操作?

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

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

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

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

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

编辑:

在第一个查询中,“order by r.validityStart”什么都不做——你可以删除它,一切都会奏效。

于 2013-03-11T07:07:20.677 回答
1

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

从文档:

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

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

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

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

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

于 2013-02-21T08:58:26.700 回答
0

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

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

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

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

于 2013-03-11T07:20:48.537 回答