一尘不染

org.hibernate.hql.ast.QueryTranslatorImpl列表注意:用集合访存指定的firstResult / maxResults;申请内存

hibernate

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

我的问题是使用setFirstResultsetMaxResult,以便带回准确的结果数。每次我看到整个结果都带回来,并且只有在使用maxResult之后。

有什么办法可以使maxResult之前吗?

非常感谢 !

这是更多信息:

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

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

我在日志中看到此消息:

2011年6月7日09:52:37
org.hibernate.hql.ast.QueryTranslatorImpl列表注意:用集合访存指定的firstResult /
maxResults;申请内存!

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

在内存中似乎恢复了200结果,并在内存中应用了maxResults之后。

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

我使用了一种解决方法,我使用maxResult进行了第一个查询,以询问订单ID,但未进行任何提取。一切正常,它使用了极限指令。在对提取使用“大”查询并将结果限制在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

阅读 319

收藏
2020-06-20

共1个答案

一尘不染

TL; DR
Hibernate不知道它需要获取指定数量的Order对象的扁平化联合查询的多少行,因此它必须将整个查询加载到内存中。请参阅下面的说明。

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

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

这些类可以这样定义:

@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将该查询作为类似于以下内容的“扁平化” 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论坛)研究此问题,那么根据Hibernate的强大功能,您会发现它实际上是一个 功能,
而不是错误。有些人实际上 想要 这种行为!

去搞清楚。

2020-06-20