我的应用程序使用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
如果先运行第一个查询,那么计数将会到来,之后如果我运行第二个查询,那么还会计数它应该给我发布实体列表。
如果我删除查询缓存它工作正常,如果我在第二个查询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"/>
答案 0 :(得分: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的结果,那么您只需要第一级缓存。
我的建议:
我认为问题可能是COUNT(r)将一个BigInteger返回给java,它不能转换为Object进行缓存。您可以在查询上调用addScalar(“count”,Hibernate.LONG)来告诉hibernate使用不同的类型 - LONG。请参阅blog.pfa-labs.com/2009/12/caching-raw-sql-count-with-hibernate.html加Is/Can Hibernate's Second-Level Cache be Used for COUNT() operations?
查询缓存应该能够处理这个问题。只有实体对象才需要二级缓存。
要非常小心您了解要尝试缓存的对象的读/写行为 - 并确保读取次数远远大于写入次数。否则缓存可能没有任何好处,甚至可能减慢速度并导致数据不一致。
请注意,某些JDBC驱动程序也会缓存数据 - 如果它们会影响JPA结果,JPA甚至都不知道它。
来自Mike Keith的“Pro JPA 2”: 大多数[JDBC]驱动程序缓存连接和语句。一些缓存还跟踪对JPA提供程序基本上透明的表或列状态,但是这样可以节省一些不必去数据库以获取每次调用的数据。只有在知道数据是只读的或者驱动程序仅控制数据库访问时,这在驱动程序中通常是可行的。
JDBC缓存(如果可用)应该可以通过特定于驱动程序的配置设置进行控制。
编辑:
在第一个查询中,“按r.validityStart排序”不执行任何操作 - 您可以删除它,一切都可以。
答案 1 :(得分:1)
查询缓存维护结果,其中查询和组合的参数构成键和值作为标识符。
来自文档:
当数据缓存没有查询结果中的id的任何缓存数据时,不使用缓存。
不会缓存导致自定义字段类型或BigDecimal或BigInteger字段投影的查询。
请注意,查询缓存不会缓存结果集中实际实体的状态; 它仅缓存标识符值和值类型的结果。查询缓存应始终与二级缓存一起使用。
最好是获取整个对象,而不是查询中的字段。
可能它忽略了查询的选择部分&amp;缓存结果。 后两部分对于两个查询都是相同的,因此产生相同的结果。您可以尝试更改查询执行顺序&amp;观察结果。
答案 2 :(得分:0)
我相信你的问题与二级缓存无关 - 这是另一回事。缓存本身不能改变预期的结果。
更确切地说,您可以在开始第二个查询之前尝试使用以下代码清除二级缓存:
session.setCacheMode(CacheMode.IGNORE); // session here is the SessionFactory
如果问题仍然存在,那么很明显二级缓存不是罪魁祸首。