org.hibernate.hql.ast.QueryTranslatorImpl list ATTENTION:使用collection fetch指定的firstResult / maxResults;在记忆中应用

时间:2011-06-06 22:03:35

标签: hibernate jpa

我遇到问题,我在JPA中有查询。因为我有一些集合,我需要使用左连接提取或内连接提取

我的问题在于使用setFirstResultsetMaxResult来恢复精确数量的结果。每次我看到整个结果都会在使用maxResult后才恢复。

以前有没有办法制作maxResult?

非常感谢!

这里有更多信息:

我的问题是当我使用它时:

startIndex = 0;
maxResults = 10;
query.setFirstResult(startIndex);
query.setMaxResults(maxResults);

我在日志中看到此消息:

  

2011年7月7日09:52:37 org.hibernate.hql.ast.QueryTranslatorImpl list   注意:firstResult / maxResults使用collection fetch指定;   在记忆中应用!

我看到200个结果回来了(在日志中),然后在HashSet中我终于得到了10个结果。

它在内存中似乎带回了200结果,并且在内存中应用了maxResults。

我正在搜索是否有任何方法可以获取并限制结果数量。

我使用了一种解决方法,我首先询问我的订单的ID,没有任何提取,使用了maxResult。 一切都很完美,它使用了极限指令。 在我使用带有fetch的“大”查询后,将id的列表中的结果限制在第一个中。

这里是我的完整查询,没有我的解决方法(注意@Bozho的谈话没有产生限制):

select o from Order  o
   left join fetch o.notes note
   left join fetch o.orderedBy orderedBy
   left join fetch orderedBy.address addressOrdered 
   left join fetch orderedBy.language orderedByLg 
   left join fetch orderedByLg.translations orderedByLgTtrad
   left join fetch o.deliveredTo deliveredTo 
   left join fetch deliveredTo.address addressDelivered 
   left join fetch deliveredTo.language deliveredToLg
   left join fetch deliveredToLg.translations 
   left join fetch o.finalReceiptPlace finalReceiptPlace
   left join fetch finalReceiptPlace.address addressFinalReceiptPlace 
   left join fetch finalReceiptPlace.language finalReceiptPlaceLg 
   left join fetch finalReceiptPlaceLg.translations
   inner join fetch o.deliveryRoute delivery
   left join fetch delivery.translations
   inner join fetch o.type orderType
   left join fetch orderType.translations 
   inner join fetch o.currency currency
   left join fetch currency.translations
   left join fetch o.attachments 
   left join fetch note.origin orig
   left join fetch orig.translations
   left join fetch o.supplier sup  
   left join fetch sup.department dep 
   left join fetch o.stateDetail stateD
   inner join fetch stateD.state stat  
where 1=1 and o.entryDate >= :startDat

3 个答案:

答案 0 :(得分:29)

  

TL; DR Hibernate不知道获取指定数量的Order对象所需的拼合,连接查询的行数,因此必须将整个查询加载到内存中。请参阅下面的解释。

要理解为什么Hibernate会这样做,你需要了解Hibernate如何处理JPA实体所涉及的ORM(对象关系映射)。

为您的订单考虑一组简化的实体。类Order包含2个字段:numbercustomerId以及订单行列表。类OrderLine包含productCodequantity字段,以及uid键和对父订单的引用。

这些类可以这样定义:

@Entity
@Table(name = "ORDER")
public class Order {
    @ID
    @Column(name = "NUMBER")
    private Integer number;
    @Column(name = "CUSTOMER_ID")
    private Integer customerId;
    @OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
    @OrderBy
    private List<OrderLine> orderLineList;

    .... // Rest of the class
}

@Entity
@Table(name = "ORDER_LINE")
public class OrderLine
{
    @ID
    @Column(name = "UID")
    private Integer uid;
    @Column(name = "PRODUCT_CODE")
    private Integer productCode;
    @Column(name = "QUANTITY")
    private Integer quantity;
    @Column(name = "ORDER_NUMBER")
    private Integer orderNumber;
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "ORDER_NUMBER", referencedColumnName = "NUMBER", insertable = false, updatable = false)
    private Order order;

    .... // Rest of the class
}

现在,如果您对这些实体执行了以下JPQL查询:

SELECT o FROM Order o LEFT JOIN FETCH o.orderLineList

然后Hibernate将此查询作为'flattened'SQL查询执行,类似于以下内容:

SELECT o.number, o.customer_id, ol.uid, ol.product_code, ol.quantity, ol.order_number
FROM order o LEFT JOIN order_line ol ON order_line.order_number = order.number

会得到这样的结果:

| o.number | o.customer_id | ol.uid | ol.product_code | ol.quantity |
|==========|===============|========|=================|=============|
| 1        | 123           | 1      | 1111            | 5           |
| 1        | 123           | 2      | 1112            | 6           |
| 1        | 123           | 3      | 1113            | 1           |
| 2        | 123           | 4      | 1111            | 2           |
| 2        | 123           | 5      | 1112            | 7           |
| 3        | 123           | 6      | 1111            | 6           |
| 3        | 123           | 7      | 1112            | 5           |
| 3        | 123           | 8      | 1113            | 3           |
| 3        | 123           | 9      | 1114            | 2           |
| 3        | 123           | 10     | 1115            | 9           |
...etc

Hibernate将使用附加的Order子对象列表'重建'OrderLine个对象。

但是,由于每个订单的订单行数是随机的,因此Hibernate无法知道要获取指定的最大Order个对象所需的此查询行数。因此,在丢弃结果集的其余部分之前,它必须采取整个查询并在内存中构建对象,直到它具有正确的数量。它产生的日志警告暗示了这一点:

ATTENTION: firstResult/maxResults specified with collection fetch; applying in memory!

我们现在才发现这些查询会对服务器内存使用产生重大影响,而且在尝试进行这些查询时,我们的服务器出现了内存错误问题。

顺便说一句,我现在要说的是,这主要是我的理论,我不知道实际的Hibernate代码是如何工作的。当你让Hibernate记录它生成的SQL语句时,你可以从日志中收集大部分内容。


<强>更新 最近我发现了上面的一些“问题”。

考虑第三个名为Shipment的实体,该实体用于订单的一个或多个行。

Shipment实体与@ManyToOne实体之间存在Order关联。

假设您有2个发货来自同一个订单,有4行。

如果您执行以下的JPQL查询:

SELECT s FROM Shipment s LEFT JOIN s.order o LEFT JOIN FETCH o.orderLineList

你会期望(或至少我做过)获得2个货件对象,每个对象都引用同一个Order对象,该对象本身将包含4行。

不,再错了!实际上,您将获得2个Shipment对象,每个对象引用相同的Order对象,其中包含 8 行!是的,行在订单中重复!是的,即使您指定了DISTINCT子句,也就是这样。

如果您在SO或其他地方(最值得注意的是Hibernate论坛)上研究此问题,您会发现这实际上是一个功能而不是一个bug,根据Hibernate的功能。有些人实际上 想要 这种行为!

去图。

答案 1 :(得分:2)

这取决于所使用的数据库引擎......

我遇到与Derby DB相同的问题setFirstResult / setMaxResults未被使用。

ROW_NUMBER() OVER()...似乎是解决方案。

不幸的是,我没有发现如何使用JPA生成此类请求而不会在SQL中使用整个请求。

答案 2 :(得分:0)

setMaxResult(10)用另一个限制结果包装查询,例如对于oracle:
选择* from(“你的查询在这里”),其中rownum&lt; 10

今天我在调查查询时发现这个问题需要64秒,现在需要1秒钟。

...对于criteria.setProjection(Projections.rowCount())也一样.setMaxResults(10)