Стратегии загрузки коллекций в Hibernate
В предыдущей статье я рассматривал детали различных типов и стратегий загрузки коллекций в JPA. В данной статье будут рассмотрены режимы загрузки коллекций в Hibernate.
Для тех, кто не читал, повторюсь: отношениям один-ко-многим или многие-ко-многим между таблицами реляционной базы данных в объектном виде соответствуют свойства сущности типа List или Set, размеченные аннотациями @OneToMany или @ManyToMany. При работе с сущностями, которые содержат коллекции других сущностей, возникает проблема известная как «N+1 selects». Первый запрос выберет только корневые сущности, а каждая связанная коллекция будет загружена отдельным запросом. Таким образом ORM выполняет N+1 SQL запросов, где N — количество корневых сущностей в результирующей выборке запроса.
Итак, самая популярная реализация JPA, Hibernate предоставляет множество способов управления загрузкой отношений один-ко-многим и многие-ко-многим.
FetchMode в Hibernate говорит как мы хотим, чтоб связанные сущности или коллекции были загружены:
— SELECT — используя по дополнительному SQL запросу на коллекцию,
— JOIN — в одном запросе с корневой сущностью, используя SQL оператор JOIN,
— SUBSELECT— в дополнительном запросе, используя SUBSELECT.
Мы также можем влиять на стратегию загрузки связанных коллекций при помощи аннотации @BatchSize (или атрибут batch-size в XML), которая устанавливает количество коллекций, которые будут загружаться в одном запросе.
Пример
Продолжим рассматривать пример из предыдущей части статьи, в котором сущность Book владеет отношениями многие-ко-многим с сущностями Author и Category. Дополним этот пример, добавив аннотации из пакета org.hibernate.annotations, которые не являются частью JPA. Пример целиком доступен на Github.
@Entity
public class Book extends AbstractBook {
@ManyToMany(fetch = FetchType.EAGER)
private List<Author> authors = new ArrayList<>();
@ManyToMany
private List<Category> categories = new ArrayList<>();
/*...*/
}
@Entity
public class BookFetchModeSelect extends AbstractBook {
@ManyToMany(fetch = FetchType.EAGER)
@Fetch(FetchMode.SELECT)
private List<Author> authors = new ArrayList<>();
@ManyToMany
@Fetch(FetchMode.SELECT)
private List<Category> categories = new ArrayList<>();
/*...*/
}
@Entity
public class BookFetchModeJoin extends AbstractBook {
@ManyToMany(fetch = FetchType.EAGER)
@Fetch(FetchMode.JOIN)
private List<Author> authors = new ArrayList<>();
@ManyToMany
@Fetch(FetchMode.JOIN)
private List<Category> categories = new ArrayList<>();
/*...*/
}
@Entity
public class BookFetchModeSubselect extends AbstractBook {
@ManyToMany(fetch = FetchType.EAGER)
@Fetch(FetchMode.SUBSELECT)
private List<Author> authors = new ArrayList<>();
@ManyToMany
@Fetch(FetchMode.SUBSELECT)
private List<Category> categories = new ArrayList<>();
/*...*/
}
@Entity
public class BookBatchSize extends AbstractBook {
@ManyToMany(fetch = FetchType.EAGER)
// Явное указание FetchMode.SELECT необходимо
// так как в Criteria API EAGER ассоциации по умолчанию загружаются с
@Fetch(FetchMode.SELECT)
@BatchSize(size = 2)
private List<Author> authors = new ArrayList<>();
@ManyToMany
@Fetch(FetchMode.SELECT)
@BatchSize(size = 2)
private List<Category> categories = new ArrayList<>();
/*...*/
}
Для более наглядной демонстрации отличий режимов загрузки необходимо добавить больше тестовых данных:
Session session = sessionFactory.getCurrentSession();
Category softwareDevelopment = new Category();
softwareDevelopment.setName("Software development");
session.persist(softwareDevelopment);
Category systemDesign = new Category();
systemDesign.setName("System design");
session.persist(systemDesign);
Author martinFowler = new Author();
martinFowler.setFullName("Martin Fowler");
session.persist(martinFowler);
AbstractBook poeaa = bookSupplier.get();
poeaa.setIsbn("007-6092019909");
poeaa.setTitle("Patterns of Enterprise Application Architecture");
poeaa.setPublicationDate(Date.from(Instant.parse("2002-11-15T00:00:00.00Z")));
poeaa.setAuthors(asList(martinFowler));
poeaa.setCategories(asList(softwareDevelopment, systemDesign));
session.persist(poeaa);
Author gregorHohpe = new Author();
gregorHohpe.setFullName("Gregor Hohpe");
session.persist(gregorHohpe);
Author bobbyWoolf = new Author();
bobbyWoolf.setFullName("Bobby Woolf");
session.persist(bobbyWoolf);
AbstractBook eip = bookSupplier.get();
eip.setIsbn("978-0321200686");
eip.setTitle("Enterprise Integration Patterns");
eip.setPublicationDate(Date.from(Instant.parse("2003-10-20T00:00:00.00Z")));
eip.setAuthors(asList(gregorHohpe, bobbyWoolf));
eip.setCategories(asList(softwareDevelopment, systemDesign));
session.persist(eip);
Category objectOrientedSoftwareDesign = new Category();
objectOrientedSoftwareDesign.setName("Object-Oriented Software Design");
session.persist(objectOrientedSoftwareDesign);
Author ericEvans = new Author();
ericEvans.setFullName("Eric Evans");
session.persist(ericEvans);
AbstractBook ddd = bookSupplier.get();
ddd.setIsbn("860-1404361814");
ddd.setTitle("Domain-Driven Design: Tackling Complexity in the Heart of Software");
ddd.setPublicationDate(Date.from(Instant.parse("2003-08-01T00:00:00.00Z")));
ddd.setAuthors(asList(ericEvans));
ddd.setCategories(asList(softwareDevelopment, systemDesign, objectOrientedSoftwareDesign));
session.persist(ddd);
Category networkingCloudComputing = new Category();
networkingCloudComputing.setName("Networking & Cloud Computing");
session.persist(networkingCloudComputing);
Category databasesBigData = new Category();
databasesBigData.setName("Databases & Big Data");
session.persist(databasesBigData);
Author pramodSadalage = new Author();
pramodSadalage.setFullName("Pramod J. Sadalage");
session.persist(pramodSadalage);
AbstractBook nosql = bookSupplier.get();
nosql.setIsbn("978-0321826626");
nosql.setTitle("NoSQL Distilled: A Brief Guide to the Emerging World of Polyglot Persistence");
nosql.setPublicationDate(Date.from(Instant.parse("2012-08-18T00:00:00.00Z")));
nosql.setAuthors(asList(pramodSadalage, martinFowler));
nosql.setCategories(asList(networkingCloudComputing, databasesBigData));
session.persist(nosql);
В тестах используется Hibernate 5.2.0.Final.
HQL запрос и FetchMode по умолчанию
Когда аннотация @Fetch не добавлена, HQL и Hibernate Criteria запросы ведут себя по-разному. В случае использования HQL запроса, по умолчанию используется FetchMode.SELECT для связанных коллекций с любым типом загрузки (EAGER и LAZY).
List books = getCurrentSession().createQuery("select b from Book b").list();
assertEquals(4, books.size());
Сгенерированный SQL:
select
book0_.id as id1_1_,
book0_.isbn as isbn2_1_,
book0_.publicationDate as publicat3_1_,
book0_.title as title4_1_
from
Book book0_
select
authors0_.Book_id as Book_id1_2_0_,
authors0_.authors_id as authors_2_2_0_,
author1_.id as id1_0_1_,
author1_.fullName as fullName2_0_1_
from
Book_Author authors0_
inner join
Author author1_
on authors0_.authors_id=author1_.id
where
authors0_.Book_id=?
select
authors0_.Book_id as Book_id1_2_0_,
authors0_.authors_id as authors_2_2_0_,
author1_.id as id1_0_1_,
author1_.fullName as fullName2_0_1_
from
Book_Author authors0_
inner join
Author author1_
on authors0_.authors_id=author1_.id
where
authors0_.Book_id=?
select
authors0_.Book_id as Book_id1_2_0_,
authors0_.authors_id as authors_2_2_0_,
author1_.id as id1_0_1_,
author1_.fullName as fullName2_0_1_
from
Book_Author authors0_
inner join
Author author1_
on authors0_.authors_id=author1_.id
where
authors0_.Book_id=?
select
authors0_.Book_id as Book_id1_2_0_,
authors0_.authors_id as authors_2_2_0_,
author1_.id as id1_0_1_,
author1_.fullName as fullName2_0_1_
from
Book_Author authors0_
inner join
Author author1_
on authors0_.authors_id=author1_.id
where
authors0_.Book_id=?
Hibernate Criteria запрос и FetchMode по умолчанию
Когда аннотация @Fetch не добавлена, в Hibernate Criteria запросах по умолчанию используется FetchMode.JOIN для связанных коллекций с типом загрузки EAGER и tchMode.SELECT для связанных коллекций с типом загрузки LAZY.
Коллекции с типом загрузки EAGER будут загружены в одном SQL запросе с корневой сущностью. Результатом запроса будет декартово произведение (cartesian product). Вместо 4 элементов в результирующей выборке, запрос вернет 6, потому что книги «Enterprise Integration Patterns» и «NoSQL» имеют двух авторов и будут дважды встречаться в результатах запроса.
Стоит отметить, что начиная с версии Hibernate 5.2 метод createCriteria класса org.hibernate.Session, который создает экземпляр org.hibernate.Criteria, считает устаревшим и помечен аннотацией @Deprecated. Вместо этого в Javadoc рекомендуется использовать JPA Criteria.
List books = getCurrentSession().createCriteria(Book.class).list(); assertEquals(6, books.size());
Сгенерированный SQL:
select
this_.id as id1_1_1_,
this_.isbn as isbn2_1_1_,
this_.publicationDate as publicat3_1_1_,
this_.title as title4_1_1_,
authors2_.Book_id as Book_id1_2_3_,
author3_.id as authors_2_2_3_,
author3_.id as id1_0_0_,
author3_.fullName as fullName2_0_0_
from
Book this_
left outer join
Book_Author authors2_
on this_.id=authors2_.Book_id
left outer join
Author author3_
on authors2_.authors_id=author3_.id
HQL и Hibernate Criteria запросы и FetchMode.SELECT
С режимом загрузки FetchMode.SELECT первый запрос выберет только корневые сущности, а каждая связанная коллекция будет загружена отдельным запросом. Так как для данного примера мы сохранили 4 сущности, то запросов будет 5. Один для загрузки сущностей Book и по одному для каждой из 4 сущностей Book для загрузки списка сущностей Author. Список сущностей Category имеет тип загрузки по умолчанию (LAZY для коллекций) и будет загружен отдельным запросом при первом обращении в коде.
List books = getCurrentSession().createQuery("select b from BookFetchModeSelect b").list();
assertEquals(4, books.size());
При режиме загрузки FetchMode.SELECT HQL и Hibernate Criteria запросы ведут себя одинаково.
List books = getCurrentSession().createCriteria(BookFetchModeSelect.class).list(); assertEquals(4, books.size());
Сгенерированный SQL:
select
bookfetchm0_.id as id1_10_,
bookfetchm0_.isbn as isbn2_10_,
bookfetchm0_.publicationDate as publicat3_10_,
bookfetchm0_.title as title4_10_
from
BookFetchModeSelect bookfetchm0_
select
authors0_.BookFetchModeSelect_id as BookFetc1_11_0_,
authors0_.authors_id as authors_2_11_0_,
author1_.id as id1_0_1_,
author1_.fullName as fullName2_0_1_
from
BookFetchModeSelect_Author authors0_
inner join
Author author1_
on authors0_.authors_id=author1_.id
where
authors0_.BookFetchModeSelect_id=?
select
authors0_.BookFetchModeSelect_id as BookFetc1_11_0_,
authors0_.authors_id as authors_2_11_0_,
author1_.id as id1_0_1_,
author1_.fullName as fullName2_0_1_
from
BookFetchModeSelect_Author authors0_
inner join
Author author1_
on authors0_.authors_id=author1_.id
where
authors0_.BookFetchModeSelect_id=?
select
authors0_.BookFetchModeSelect_id as BookFetc1_11_0_,
authors0_.authors_id as authors_2_11_0_,
author1_.id as id1_0_1_,
author1_.fullName as fullName2_0_1_
from
BookFetchModeSelect_Author authors0_
inner join
Author author1_
on authors0_.authors_id=author1_.id
where
authors0_.BookFetchModeSelect_id=?
select
authors0_.BookFetchModeSelect_id as BookFetc1_11_0_,
authors0_.authors_id as authors_2_11_0_,
author1_.id as id1_0_1_,
author1_.fullName as fullName2_0_1_
from
BookFetchModeSelect_Author authors0_
inner join
Author author1_
on authors0_.authors_id=author1_.id
where
authors0_.BookFetchModeSelect_id=?<pre>
<h2>HQL и Hibernate Criteria запросы и FetchMode.SUBSELECT</h2>
При режиме загрузки <code>FetchMode.SUBSELECT</code> будет выполнено 2 SQL запроса. Первый загрузит корневые сущности, а второй - связанные коллекции для всех корневых сущностей из результатов первого SQL запроса, используя подзапрос.
<pre>List books = getCurrentSession().createQuery("select b from BookFetchModeSubselect b").list();
assertEquals(4, books.size());
HQL и Hibernate Criteria запросы ведут себя одинаково и при режиме загрузки FetchMode.SUBSELECT.
List books = getCurrentSession().createCriteria(BookFetchModeSubselect.class).list(); assertEquals(4, books.size());
Сгенерированный SQL:
select
bookfetchm0_.id as id1_13_,
bookfetchm0_.isbn as isbn2_13_,
bookfetchm0_.publicationDate as publicat3_13_,
bookfetchm0_.title as title4_13_
from
BookFetchModeSubselect bookfetchm0_
select
authors0_.BookFetchModeSubselect_id as BookFetc1_14_1_,
authors0_.authors_id as authors_2_14_1_,
author1_.id as id1_0_0_,
author1_.fullName as fullName2_0_0_
from
BookFetchModeSubselect_Author authors0_
inner join
Author author1_
on authors0_.authors_id=author1_.id
where
authors0_.BookFetchModeSubselect_id in (
select
bookfetchm0_.id
from
BookFetchModeSubselect bookfetchm0_
)
HQL запрос и FetchMode.JOIN
Поведение HQL запросов при режиме загрузке FetchMode.JOIN, на первый взгляд, немного неожиданное. Вместо того, чтобы загрузить связанные коллекции, помеченные аннотацией @Fetch(FetchMode.JOIN), в одном запросе с корневыми сущностями, используя SQL оператор JOIN, HQL запрос транслируется в несколько SQL запросов по типа FetchMode.SELECT. Но в отличии от FetchMode.SELECT, при FetchMode.JOIN будет игнорироваться указанный тип загрузки (LAZY и EAGER) и все коллекции будут загружены сразу, а не при первом обращении в коде (поведение соответствующее типу EAGER).
Таким образом в следующем примере будет выполнено 8 SQL запросов. Один запрос для загрузки корневых сущностей Book и для каждой из 4 корневых сущностей из результатов первого SQL запроса по одному запросу для загрузки списка сущностей Author и по одному запросу для загрузки списка сущностей Category.
List books = getCurrentSession().createQuery("select b from BookFetchModeJoin b").list();
assertEquals(4, books.size());
Сгенерированный SQL:
select
bookfetchm0_.id as id1_7_,
bookfetchm0_.isbn as isbn2_7_,
bookfetchm0_.publicationDate as publicat3_7_,
bookfetchm0_.title as title4_7_
from
BookFetchModeJoin bookfetchm0_
select
categories0_.BookFetchModeJoin_id as BookFetc1_9_0_,
categories0_.categories_id as categori2_9_0_,
category1_.id as id1_16_1_,
category1_.description as descript2_16_1_,
category1_.name as name3_16_1_
from
BookFetchModeJoin_Category categories0_
inner join
Category category1_
on categories0_.categories_id=category1_.id
where
categories0_.BookFetchModeJoin_id=?
select
authors0_.BookFetchModeJoin_id as BookFetc1_8_0_,
authors0_.authors_id as authors_2_8_0_,
author1_.id as id1_0_1_,
author1_.fullName as fullName2_0_1_
from
BookFetchModeJoin_Author authors0_
inner join
Author author1_
on authors0_.authors_id=author1_.id
where
authors0_.BookFetchModeJoin_id=?
select
categories0_.BookFetchModeJoin_id as BookFetc1_9_0_,
categories0_.categories_id as categori2_9_0_,
category1_.id as id1_16_1_,
category1_.description as descript2_16_1_,
category1_.name as name3_16_1_
from
BookFetchModeJoin_Category categories0_
inner join
Category category1_
on categories0_.categories_id=category1_.id
where
categories0_.BookFetchModeJoin_id=?
select
authors0_.BookFetchModeJoin_id as BookFetc1_8_0_,
authors0_.authors_id as authors_2_8_0_,
author1_.id as id1_0_1_,
author1_.fullName as fullName2_0_1_
from
BookFetchModeJoin_Author authors0_
inner join
Author author1_
on authors0_.authors_id=author1_.id
where
authors0_.BookFetchModeJoin_id=?
select
categories0_.BookFetchModeJoin_id as BookFetc1_9_0_,
categories0_.categories_id as categori2_9_0_,
category1_.id as id1_16_1_,
category1_.description as descript2_16_1_,
category1_.name as name3_16_1_
from
BookFetchModeJoin_Category categories0_
inner join
Category category1_
on categories0_.categories_id=category1_.id
where
categories0_.BookFetchModeJoin_id=?
select
authors0_.BookFetchModeJoin_id as BookFetc1_8_0_,
authors0_.authors_id as authors_2_8_0_,
author1_.id as id1_0_1_,
author1_.fullName as fullName2_0_1_
from
BookFetchModeJoin_Author authors0_
inner join
Author author1_
on authors0_.authors_id=author1_.id
where
authors0_.BookFetchModeJoin_id=?
select
categories0_.BookFetchModeJoin_id as BookFetc1_9_0_,
categories0_.categories_id as categori2_9_0_,
category1_.id as id1_16_1_,
category1_.description as descript2_16_1_,
category1_.name as name3_16_1_
from
BookFetchModeJoin_Category categories0_
inner join
Category category1_
on categories0_.categories_id=category1_.id
where
categories0_.BookFetchModeJoin_id=?
select
authors0_.BookFetchModeJoin_id as BookFetc1_8_0_,
authors0_.authors_id as authors_2_8_0_,
author1_.id as id1_0_1_,
author1_.fullName as fullName2_0_1_
from
BookFetchModeJoin_Author authors0_
inner join
Author author1_
on authors0_.authors_id=author1_.id
where
authors0_.BookFetchModeJoin_id=?
HQL запрос с «join fetch»
Чтобы корневые сущности со связанными коллекциями были загружены в одном SQL запросе, используйте оператор JOIN FETCH. Результатом запроса будет декартово произведение. Вместо 4 элементов в результирующей выборке, запрос вернет 6, потому что книги «Enterprise Integration Patterns» и «NoSQL» имеют двух авторов и будут дважды встречаться в результатах запроса. Коллекции, которые не были присоединены оператором JOIN FETCH, будут загружены согласно типу и режиму загрузки.
В данном примере будет выполнено по дополнительному запросу на каждую корневую сущность из списка результатов для загрузки списка сущностей Category.
List books = getCurrentSession().createQuery("select b from BookFetchModeJoin b join fetch b.authors a").list();
assertEquals(6, books.size());
Сгенерированный SQL:
select
bookfetchm0_.id as id1_7_0_,
author2_.id as id1_0_1_,
bookfetchm0_.isbn as isbn2_7_0_,
bookfetchm0_.publicationDate as publicat3_7_0_,
bookfetchm0_.title as title4_7_0_,
author2_.fullName as fullName2_0_1_,
authors1_.BookFetchModeJoin_id as BookFetc1_8_0__,
authors1_.authors_id as authors_2_8_0__
from
BookFetchModeJoin bookfetchm0_
inner join
BookFetchModeJoin_Author authors1_
on bookfetchm0_.id=authors1_.BookFetchModeJoin_id
inner join
Author author2_
on authors1_.authors_id=author2_.id
select
categories0_.BookFetchModeJoin_id as BookFetc1_9_0_,
categories0_.categories_id as categori2_9_0_,
category1_.id as id1_16_1_,
category1_.description as descript2_16_1_,
category1_.name as name3_16_1_
from
BookFetchModeJoin_Category categories0_
inner join
Category category1_
on categories0_.categories_id=category1_.id
where
categories0_.BookFetchModeJoin_id=?
select
categories0_.BookFetchModeJoin_id as BookFetc1_9_0_,
categories0_.categories_id as categori2_9_0_,
category1_.id as id1_16_1_,
category1_.description as descript2_16_1_,
category1_.name as name3_16_1_
from
BookFetchModeJoin_Category categories0_
inner join
Category category1_
on categories0_.categories_id=category1_.id
where
categories0_.BookFetchModeJoin_id=?
select
categories0_.BookFetchModeJoin_id as BookFetc1_9_0_,
categories0_.categories_id as categori2_9_0_,
category1_.id as id1_16_1_,
category1_.description as descript2_16_1_,
category1_.name as name3_16_1_
from
BookFetchModeJoin_Category categories0_
inner join
Category category1_
on categories0_.categories_id=category1_.id
where
categories0_.BookFetchModeJoin_id=?
select
categories0_.BookFetchModeJoin_id as BookFetc1_9_0_,
categories0_.categories_id as categori2_9_0_,
category1_.id as id1_16_1_,
category1_.description as descript2_16_1_,
category1_.name as name3_16_1_
from
BookFetchModeJoin_Category categories0_
inner join
Category category1_
on categories0_.categories_id=category1_.id
where
categories0_.BookFetchModeJoin_id=?
Hibernate Criteria запрос и FetchMode.JOIN
В Hibernate Criteria запросах связанные коллекции с режимом загрузки FetchMode.JOIN будут загружены сразу (тип загрузки EAGER) и в одном запросе с корневыми сущностями при помощи SQL оператора JOIN. Как уже рассматривалось в прошлой статье, только одна коллекций, которая загружается со стратегией JOIN может быть типа java.util.List, остальные коллекции, которые загружаются стратегией JOIN должны быть типа java.util.Set, иначе будет выброшено исключение org.hibernate.loader.MultipleBagFetchException.
List books = getCurrentSession().createCriteria(BookFetchModeJoin.class).list();
Будет выброшено исключение:org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
HQL и Hibernate Criteria запросы и FetchMode.SELECT с @BatchSize
@BatchSize устанавливает количество коллекций, которые должны быть загружены в одном SQL запросе. Если результат запроса содержит 4 сущности, каждая из которых имеет по связанной коллекции, при режим загрузки SELECT будет выполнено 5 запросов. Один запрос для загрузки всех корневых сущностей и по одному для загрузки связанной коллекции каждой из 4 корневых сущностей. @BatchSize(size = 2) указывает ORM загружать по 2 связанные коллекции в одном запросе. Таким образом, всего будет выполнено 3 запроса вместо 5. Один запрос для загрузки всех корневых сущностей и еще 2 запроса, каждый из которых загрузит по 2 связанные коллекции для 2 корневых сущностей.
List books = getCurrentSession().createQuery("select b from BookBatchSize b").list();
assertEquals(4, books.size());
Как уже отмечалось ранее, при режиме загрузки FetchMode.SELECT HQL и Hibernate Criteria запросы ведут себя одинаково. При использовании @BatchSize поведение также будет одинаковым.
List books = getCurrentSession().createCriteria(BookBatchSize.class).list(); assertEquals(4, books.size());
Сгенерированный SQL:
select
bookbatchs0_.id as id1_4_,
bookbatchs0_.isbn as isbn2_4_,
bookbatchs0_.publicationDate as publicat3_4_,
bookbatchs0_.title as title4_4_
from
BookBatchSize bookbatchs0_
select
authors0_.BookBatchSize_id as BookBatc1_5_1_,
authors0_.authors_id as authors_2_5_1_,
author1_.id as id1_0_0_,
author1_.fullName as fullName2_0_0_
from
BookBatchSize_Author authors0_
inner join
Author author1_
on authors0_.authors_id=author1_.id
where
authors0_.BookBatchSize_id in (
?, ?
)
select
authors0_.BookBatchSize_id as BookBatc1_5_1_,
authors0_.authors_id as authors_2_5_1_,
author1_.id as id1_0_0_,
author1_.fullName as fullName2_0_0_
from
BookBatchSize_Author authors0_
inner join
Author author1_
on authors0_.authors_id=author1_.id
where
authors0_.BookBatchSize_id in (
?, ?
)
Выводы
Существует немало нюансов, связанных со стратегиями загрузки связанных коллекций в JPA и Hibernate, многие из которых были рассмотрены в этой и предыдущей статьях.
Не стоит использовать одну выбранную стратегию загрузки повсюду в приложении. Каждый случай надо проанализировать индивидуально и выбрать оптимальную стратегию загрузки. Как это часто бывает, в большинстве случаев стратегия загрузки по умолчанию будет оптимальным вариантом. Если приложение испытывает проблемы с производительностью, которые вызваны неправильным выбором стратегии загрузки, для анализа пригодятся следующие свойства Hibernate конфигурации: hibernate.show_sql=true, hibernate.format_sql=true и hibernate.use_sql_comments=true.