Рубрики
Без рубрики

Лучший способ получить несколько объектов по идентификатору с помощью JPA и гибернации

Узнайте, как лучше всего извлекать несколько объектов по их идентификатору при использовании JPA, Hibernate и оптимизации in_clause_parameter_padding.

Автор оригинала: Vlad Mihalcea.

Вступление

В этой статье мы рассмотрим, как мы можем загружать несколько объектов по идентификатору одновременно при использовании JPA и Hibernate.

Загрузка нескольких объектов по их идентификатору является очень распространенным требованием при использовании JPA и Hibernate. Следовательно, мы увидим, как мы можем оптимизировать выполнение базового SQL-запроса.

Модель предметной области

Для следующих примеров мы будем использовать следующую Книгу сущность:

Теперь давайте рассмотрим, что мы добавили несколько Книг сущностей в нашу базу данных:

entityManager.persist(
    new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea")
);

entityManager.persist(
    new Book()
    .setIsbn("978-1934356555")
    .setTitle("SQL Antipatterns")
    .setAuthor("Bill Karwin")
);

entityManager.persist(
    new Book()
    .setIsbn("978-3950307825")
    .setTitle("SQL Performance Explained")
    .setAuthor("Markus Winand")
);

entityManager.persist(
    new Book()
    .setIsbn("978-1449373320")
    .setTitle("Designing Data-Intensive Applications")
    .setAuthor("Martin Kleppmann")
);

Обратите внимание, как хорошо мы можем создавать экземпляры сущностей JPA, когда атрибуты соответствуют рекомендациям API в стиле fluent.

Для получения более подробной информации об использовании шаблона проектирования API в стиле fluent ознакомьтесь с этой статьей .

Извлечение нескольких объектов по идентификатору с помощью JPA

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

При использовании JPA мы можем загружать несколько объектов одновременно, используя JPQL или API критериев.

Извлечение нескольких объектов по идентификатору с помощью JPQL

Чтобы извлечь несколько сущностей с помощью запроса JPQL, нам нужно передать идентификаторы сущностей в предложение IN, как показано в следующем примере:

List books = entityManager.createQuery("""
    select b
    from Book b
    where b.isbn in (:isbn)
    """, Book.class)
.setParameter(
    "isbn", 
    Arrays.asList(
        "978-9730228236",
        "978-1934356555",
        "978-3950307825"
    )
)
.getResultList();

Теперь при выполнении приведенного выше запроса JPQL Hibernate генерирует следующий SQL-запрос:

Query:["
    SELECT 
        b.isbn AS isbn1_0_,
        b.author AS author2_0_,
        b.title AS title3_0_
    FROM 
        book b
    WHERE 
        b.isbn IN (
            ?,
            ?,
            ?
        )
 "], 
Params:[(
    978-9730228236, 
    978-1934356555, 
    978-3950307825
)]

Лучший способ регистрации инструкций SQL-это использовать JDBC источник данных или Драйвер прокси-сервер в качестве проекта с открытым исходным кодом datasourceproxy .

Для получения более подробной информации об этой теме ознакомьтесь с этой статьей .

Теперь, если вы измените количество параметров предложения IN, вы увидите, что инструкция SQL должна соответствовать количеству параметров привязки, и это может повлиять на эффективность механизма кэширования инструкций SQL. Чтобы справиться с этой проблемой, я добавил поддержку hibernate.запрос.in_clause_parameter_padding свойство конфигурации.

Итак, при включении спящий режим.запрос.in_clause_parameter_padding свойство конфигурации:


    name="hibernate.query.in_clause_parameter_padding"
    value="true"

Hibernate будет использовать ближайшее целое число степени два, которое может содержать все предоставленные значения параметров привязки. Поэтому при повторном запуске предыдущего запроса JPQL Hibernate выполнит следующую инструкцию SQL:

Query:["
    SELECT 
        b.isbn AS isbn1_0_,
        b.author AS author2_0_,
        b.title AS title3_0_
    FROM 
        book b
    WHERE 
        b.isbn IN (
            ?,
            ?,
            ?,
            ?
        )
 "], 
Params:[(
    978-9730228236, 
    978-1934356555, 
    978-3950307825,
    978-3950307825
)]

Обратите внимание, как 4 – й параметр был продублирован в наборе значений параметра привязки. Это позволит нам повторно использовать один и тот же план выполнения инструкции SQL, даже если мы приведем четыре аргумента, как показано в следующем запросе JPQL:

List books = entityManager.createQuery(
    select b
    from Book b
    where b.isbn in (:isbn)
    """, Book.class)
.setParameter(
    "isbn", 
    Arrays.asList(
        "978-9730228236",
        "978-1934356555",
        "978-3950307825",
        "978-1449373320"
    )
)
.getResultList();

Спящий режим.запрос.in_clause_parameter_padding оптимизация особенно полезна при использовании системы баз данных, предоставляющей кэш плана выполнения SQL, например Oracle, SQL Server или DB2.

Для получения более подробной информации о hibernate.запрос.in_clause_parameter_padding параметр конфигурации, проверьте эту статью .

Извлечение нескольких объектов по идентификатору с помощью API критериев

Предыдущий запрос JPQL также может быть написан с использованием API критериев JPA, как показано в следующем примере:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery query = builder.createQuery(Book.class);

ParameterExpression isbn = builder.parameter(List.class);

Root root = query
.from(Book.class);

query
.where(
    root.get("isbn").in(
        isbn
    )
);

List books = entityManager
.createQuery(query)
.setParameter(
    isbn, 
    Arrays.asList(
        "978-9730228236",
        "978-1934356555",
        "978-3950307825"
    )
)
.getResultList();

Как и в случае с JPQL-запросами, вы можете использовать hibernate.query.in_clause_parameter_padding оптимизация для повышения эффективности механизма кэширования инструкций SQL.

Извлечение нескольких объектов по идентификатору с помощью Hibernate

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

List books = entityManager
.unwrap(Session.class)
.byMultipleIds(Book.class)
.multiLoad(
    "978-9730228236",
    "978-1934356555",
    "978-3950307825"
);

При выполнении приведенного выше примера MultiLoad Hibernate создаст следующий SQL-запрос:

Query:["
    SELECT 
        b.isbn AS isbn1_0_,
        b.author AS author2_0_,
        b.title AS title3_0_
    FROM 
        book b
    WHERE 
        b.isbn IN (
            ?,
            ?,
            ?
        )
 "], 
Params:[(
    978-9730228236, 
    978-1934356555, 
    978-3950307825
)]

Хотя метод MultiLoad намного более лаконичен, чем запрос JPQL или API критериев, в настоящее время он не использует hibernate.запрос.in_clause_parameter_padding оптимизация. Тем не менее, я открыл для этого проблему HHH-13692 Jira, и это должно быть исправлено в будущей версии Hibernate.

Еще одним преимуществом использования метода MultiLoad является то, что сущности могут быть загружены из кэша первого или второго уровня без необходимости выполнения SQL-запроса.

Вывод

Извлечение нескольких объектов по их идентификатору является типичным требованием при использовании JPA и Hibernate. Хотя использование запроса JPQL или API критериев может показаться немного подробным по сравнению с альтернативой Hibernate MultiLoad , если вы используете систему баз данных, предлагающую кэш плана выполнения SQL, то лучше придерживаться подхода JPA-запросов при включении hibernate.запрос.in_clause_parameter_padding оптимизация.