TL; DR Hibernate не знает, сколько строк в плоском объединенном запросе ему нужно, чтобы получить указанное число объектов Order, поэтому он должен загрузить весь запрос вобъем памяти.Ниже приведено объяснение.
Чтобы понять, почему Hibernate делает это, вам необходимо понять, как Hibernate использует ORM (объектно-реляционное отображение) для объектов JPA.
Рассмотримупрощенный набор сущностей для вашего заказа.Класс Order
содержит 2 поля: number
и customerId
и список строк заказа.Класс OrderLine
содержит поля productCode
и quantity
, а также клавишу 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 выполнит этот запрос как «плоский» 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 объекта отгрузки, каждый со ссылкой на один и тот же объект Заказа, который сам содержал бы 4 строки.
Нет, опять не так!Фактически, вы получаете 2 объекта Отправления, каждый из которых ссылается на один и тот же объект Заказа, который содержит 8 Линии!Да, Линии дублируются в Ордене!И да, это даже если вы укажете предложение DISTINCT.
Если вы исследуете эту проблему здесь, на SO или в другом месте (особенно на форумах Hibernate), вы обнаружите, что на самом деле это функция не баг, согласно способностям Hibernate.Некоторые люди на самом деле хотят такое поведение!
Иди на цифру.