×Закрыть

Стратегии загрузки коллекций в 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.

13 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.
Как это часто бывает, в большинстве случаев стратегия загрузки по умолчанию будет оптимальным вариантом

Вы серьезно??? Если у вас приложение с дефолтными настройками и вы просто поставили аннотации и у вас будет все ок?? Я даю 99% если это так и у вас более менее не тривиальное CRUD приложение то оно работает медленно.

99%? Почему не 99.999%? Это демагогия.
Значения по-умолчанию берутся не с потолка, а чаще всего в результате анализа и так, чтобы удовлетворять наиболее частому сценарию использованию по мнению авторов.

Если у вас приложение с дефолтными настройками и вы просто поставили аннотации и у вас будет все ок??
Все зависит от модели данных и запросов, которые будут выполняться.
В данной статье рассматриваются разные режими загрузки и отличия в SQL запросах, которые будут сгенерированны. На основании примеров читатели сами могут выбрать оптимальный для их конкретного случая набор настроек. Если говорить о производительности и проблемах с производительностью, которые взваны базой даных, то я более подробно рассматривал этот вопрос в своем докладе www.slideshare.net/...-databaserelated-problems

А ничего что фреймворк был создан давно и никто не станет менять его в ущерб обратной совместимости, никто не идеален и все допускают ошибки. Проблемы с производительностью будут если неправильно использовать ORM фреймворк, мы же сейчас о нем, и не делать тюнинг инструментов с которыми вы работаете. А так же нужно хорошо понимать как работает ваш инструмент и к чему это может привести.

На основании примеров читатели сами могут выбрать оптимальный для их конкретного случая набор настроек.

Так вот читателем полезно было бы практических применений и мыслей автора когда что нужно использовать.

А ничего что фреймворк был создан давно и никто не станет менять его в ущерб обратной совместимости
Нет, ничего. Многие фреймворки меняются в ущерб обратной совместимости. EJB2 и EJB3, например. jQuery 2+ перестал поддерживать устаревшие браузеры и т.д.
Практические рекомендации по решению проблем с производительностью достаточно много в докладе на SlideShare по ссылке в моем предыдущем коментарии. В концепцию данной статьи практические рекомендации не впысываются.

Причем тут EJB и jQuery, я вам пишу про Hibernate.
Спасибо за slide share, но мы сейчас про другое.
Ну понятно что не вписываются, я же о том же.

При том, что это примеры давно созданных фреймворков, которые поменялись в ущерб обратной совместимости.

Я еще в первом ответе на комментарий, отметил, что это демагогия. Не были озвучены значения по умолчанию, которые «никто не будет менять в ущерб обратной совместимости». Так что в данном контекте пример с EJB и jQuery вполне уместен.

Та кому это интересно, напишите ребятам с Hibernate что они все делают не так.

Какой толк с вашей статьи если вы ничего нового от себя не написали, взяли материал с книги и перевели его на русский?? Более интересно когда вы показываете реальный опыт с жизни и даете советы как и что использовать основываясь на своем опыте. Вы написали теорию на поверхностном уровне. Где подводные камни и проблемы с которыми вы столкнулись? После прочтения статьи нет четкого понимания что и когда лучше использовать, какой batch size ставить и почему, когда subselect, а когда join и так далее.

Да и @BatchSize можно использовать над классом, а не только над коллекцией. И не понятно из статьи как этот batch size работает, какой алгоритм генерации запроса с IN? В вашем примере batchSize = 2
Хибернейт сгенерирует два массива запросов с IN: in (?) и in(?,?). Алгоритм можно посмотреть здесь — org.hibernate.internal.util.collections.ArrayHelper getBatchSizes(int maxBatchSize).

Так же есть бага с FetchMode.SUBSELECT и pagination, в один прекрасный момент вы можете положить ваше приложение. И subselect работает на запоминании запроса и получается ситуация что сложно контролировать запросы в приложении и если запрос выполняется не очень быстро и он потом еще скопируется в IN секцию — не очень хороший перформенс будет.
Я бы не советовал использовать Subselect вообще.

Из какой книги я взял материал и перевел на русский? Бага с FetchMode.SUBSELECT известна? Что пишут в Hibernate Jira, в какой версии планируют пофиксить баг?
Я не стал советовать всегда использовать один FetchType и никогда не использовать другой, потому что это непрофессионально. Читатели могут узнать различия разных режимов загрузки, посмотреть примеры и принять решение для своего конкретного случая.

Вот бага — hibernate.atlassian.net/browse/HHH-2666
Она открыта давно и известна, никто не планирует ее фиксить.
Вы не стали советовать и не предостерегли их и не написали ситуации в какой лучше использовать тот или иной способ.
Книга например — Java Persistence with Hibernate 2nd edition 2016. Надеюсь вы читали ее.

Спасибо за ссылку на багу. Теперь у всех будет возможность ознакомиться с историей обсуждения этой проблемы и почему она не была решена до сих пор. Сейчас она классифицирована, как Minor. 21 Vote for this issue.

Какой раздел или какие страницы из этой книги я перевел на русский язык? Я хочу разъяснить этот вопрос, так как не все люди, которые читают комментарии хорошо знакомы с этой книгой и, прочитав ваш комментарий, могут подумать, что моя авторская статья является переводом, а это не так.

Я о том что есть куча статей и в книге написано про то что вы написали, и хотелось бы увидеть практических примеров, а не просто теорию. Еще и на DOU

Спасибо за статьи, познавательно.

Подписаться на комментарии