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

Как работает стратегия автоматической очистки в JPA и спящем режиме

Режим автоматической очистки Hibernate работает по-разному, независимо от того, загружаете ли вы Hibernate через JPA или используете автономный механизм.

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

Вступление

Режим автоматической очистки Hibernate работает по-разному, независимо от того, загружаете ли вы Hibernate через JPA или используете автономный механизм.

При использовании JPA режим автоматической очистки приводит к тому, что все запросы (JPA, API критериев и собственный SQL) запускают очистку до выполнения запроса. Однако это не тот случай, когда при загрузке в спящий режим используется собственный API.

Не все запросы запускают сброс сеанса

Многие предположили бы, что Hibernate всегда сбрасывает сеанс перед любым выполняемым запросом. Хотя это, возможно , был более интуитивный подход и, вероятно, ближе к типу автоматической очистки JPA , Hibernate пытается оптимизировать это. Если выполняемый в данный момент запрос не попадет в ожидающие инструкции SQL INSERT/UPDATE/DELETE, то сброс строго не требуется.

Как указано в справочной документации , стратегия автоматической очистки может иногда синхронизировать текущий контекст сохранения до выполнения запроса. Было бы более интуитивно понятно, если бы авторы фреймворка решили назвать его FlushMode. ИНОГДА.

JPQL/HQL и SQL

Как и многие другие решения ORM, Hibernate предлагает ограниченный язык запросов сущностей ( JPQL /|/HQL ) это во многом основано на синтаксисе SQL-92 .

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

Хотя вы можете использовать язык запросов сущностей во многих случаях использования (выбор сущностей и даже проекций), бывают случаи, когда его ограниченные возможности не соответствуют расширенному запросу на запрос. Всякий раз, когда мы хотим использовать некоторые конкретные методы запроса, такие как:

у нас нет другого выбора, кроме как запускать собственные SQL-запросы.

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

АВТОМАТИЧЕСКАЯ очистка и HQL/JPQL

Сначала мы проверим, как ведет себя режим автоматической очистки при выполнении запроса HQL. Для этого мы определяем следующие несвязанные сущности:

В ходе теста будут выполнены следующие действия:

  • Объект Продукт будет сохранен.
  • Извлечение сущности User не должно вызывать сброс контекста сохранения.
  • При запросе сущности Product автозапуск должен инициировать синхронизацию перехода состояния сущности (инструкция INSERT для строки product таблицы должна быть выполнена до выполнения запроса SELECT).
Product product = new Product();
product.setColor("Blue");
session.persist(product);

assertEquals(
    0L,  
    session.createQuery("select count(id) from User").getSingleResult()
);
assertEquals(
    product.getId(), 
    session.createQuery("select p.id from Product p").getSingleResult()
);

Выдача следующего вывода SQL:

SELECT count(user0_.id) AS col_0_0_
FROM USER user0_

INSERT INTO product (color, id)
VALUES ('Blue', 'f76f61e2-f3e3-4ea4-8f44-82e9804ceed0')
 
SELECT product0_.id AS col_0_0_
FROM product product0_

Как вы можете видеть, выбор пользователя не вызвал сброс сеанса. Это связано с тем, что Hibernate проверяет текущее пространство запросов на соответствие ожидающим операторам таблицы. Если выполняемый в данный момент запрос не перекрывается с операторами незаполненной таблицы, сброс можно безопасно игнорировать.

HQL может вызвать Продукт сброс даже для подвыборок:

session.persist(product);

assertEquals(
    0L,  
    session.createQuery(
        "select count(*) " +
        "from User u " +
        "where u.favoriteColor in (" + 
        "    select distinct(p.color) from Product p" + 
        ")"
     ).getSingleResult()
);

Что приводит к правильному вызову сброса:

INSERT INTO product (color, id)
VALUES ('Blue', '2d9d1b4f-eaee-45f1-a480-120eb66da9e8')

SELECT count(*) AS col_0_0_
FROM USER user0_
WHERE user0_.favoriteColor IN (
    SELECT DISTINCT product1_.color
    FROM product product1_
)

Режим гибернации также может вызвать сброс продукта даже для запроса на соединение в стиле тета:

session.persist(product);

assertEquals(
    0L,  
    session.createQuery(
        "select count(*) " +
        "from User u, Product p " +
        "where u.favoriteColor = p.color"
    ).getSingleResult()
);

Запуск ожидаемого сброса:

INSERT INTO product (color, id)
VALUES ('Blue', '4af0b843-da3f-4b38-aa42-1e590db186a9')

SELECT count(*) AS col_0_0_
FROM USER user0_
CROSS JOIN product product1_
WHERE user0_.favoriteColor=product1_.color

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

Таким образом, Hibernate знает только о тех таблицах, на которые мы явно ссылались в нашем запросе HQL. Если текущие ожидающие утверждения DML подразумевают триггеры базы данных или каскадирование на уровне базы данных, Hibernate не будет знать об этом. Таким образом, даже для HQL режим автоматической очистки может вызвать проблемы с согласованностью.

АВТОМАТИЧЕСКАЯ очистка и собственные SQL-запросы

Когда дело доходит до собственных SQL-запросов, все становится намного сложнее. Hibernate не может анализировать SQL-запросы, поскольку он поддерживает только ограниченный синтаксис запросов к базе данных. Многие системы баз данных предлагают собственные функции, которые выходят за рамки возможностей запроса сущностей в режиме гибернации.

Запрос таблицы Product с помощью собственного SQL-запроса не вызовет сброс, что приведет к проблеме несоответствия:

Product product = new Product();
product.setColor("Blue");
session.persist(product);

assertEquals(
    0, 
    session.createNativeQuery("SELECT COUNT(*) FROM product").getSingleResult()
);
SELECT COUNT(*)
FROM product

INSERT INTO product (color, id)
VALUES ('Blue', '718b84d8-9270-48f3-86ff-0b8da7f9af7c')

Вновь сохраненный продукт был вставлен только во время фиксации транзакции, поскольку собственный SQL-запрос не вызвал сброс. Это серьезная проблема согласованности, которую трудно отладить или даже предвидеть многими разработчиками. Это еще одна причина для того, чтобы всегда проверять автоматически сгенерированные инструкции SQL .

Такое же поведение наблюдается даже для именованных собственных запросов:

@NamedNativeQuery(name = "product_ids", query = "SELECT COUNT(*) FROM product")

В этом случае мы не можем видеть вновь добавленную продукцию

assertEquals(0, session.getNamedQuery("product_ids").getSingleResult());

Таким образом, даже если SQL-запрос предварительно загружен, Hibernate не будет извлекать связанное пространство запросов для сопоставления его с ожидающими операторами DML.

Стоит отметить, что это поведение применимо к API, зависящему от режима гибернации, а не к режиму автоматической очистки JPA.

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

Отклонение текущей стратегии промывки

Даже если текущий сеанс определяет стратегию очистки по умолчанию, вы всегда можете переопределить ее на основе запроса.

Режим сброса запросов

Режим ВСЕГДА будет очищать контекст сохранения перед выполнением любого запроса (HQL или SQL). На этот раз Hibernate не применяет оптимизацию, и все ожидающие перехода состояния сущности будут синхронизированы с текущей транзакцией базы данных.

assertEquals(
    product.getId(), 
    session.createNativeQuery("select id from product")
    .setFlushMode(FlushMode.ALWAYS)
    .getSingleResult()
);

Инструкция по переходу в спящий режим, какие таблицы должно быть синхронизировано

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

assertEquals(
    product.getId(), 
    session.createNativeQuery(
        "select id from product")
    .addSynchronizedEntityClass(Product.class)
    .getSingleResult());

Вывод

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

Мое предложение состоит в том, чтобы использовать режим ВСЕГДА промывки, так как он ближе к тому, как JPA определяет AUTO My suggestion is to use the ALWAYS flush mode, since it's closer to how JPA defines the AUTO

Непоследовательность-это гораздо большая проблема, чем некоторые случайные преждевременные приливы. В то время как смешивание операций DML и запросов может привести к ненужной очистке, эту ситуацию не так сложно смягчить. Во время транзакции лучше всего выполнять запросы в начале (когда не требуется синхронизировать переходы в состояние ожидающей сущности) и ближе к концу транзакции (когда текущий контекст сохранения все равно будет сброшен).

Операции перехода состояния сущности следует перемещать ближе к концу транзакции, стараясь избежать их чередования с операциями запроса (таким образом предотвращая преждевременный запуск сброса).