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

Как Hibernate гарантирует повторяемое чтение на уровне приложения

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

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

Все сущности загружаются в контексте сеанса гибернации, действуя как кэш с последующей записью транзакций .

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

Если разговор охватывает несколько запросов, мы можем выполнять повторяемые чтения на уровне приложения. Длинные разговоры по своей сути зависят от состояния, поэтому мы можем выбрать отдельные объекты или длительные контексты сохранения . Но повторяемые чтения на уровне приложения требуют стратегии управления параллелизмом на уровне приложения, такой как оптимистическая блокировка.

Но такое поведение иногда может оказаться неожиданным .

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

В этом примере мы видим, что кэш первого уровня предотвращает перезапись уже загруженного объекта. Чтобы доказать такое поведение, я придумал следующий тестовый пример:

doInTransaction(session -> {
    Product product = new Product();
    product.setId(1L);
    product.setQuantity(7L);
    session.persist(product);
});
doInTransaction(session -> {
    final Product product = (Product) session.get(Product.class, 1L);
    try {
        executeSync(() -> doInTransaction(_session -> {
            Product otherThreadProduct = (Product) _session.get(Product.class, 1L);
            assertNotSame(product, otherThreadProduct);
            otherThreadProduct.setQuantity(6L);
        }));
        Product reloadedProduct = (Product) session.createQuery("from Product").uniqueResult();
        assertEquals(7L, reloadedProduct.getQuantity());
        assertEquals(6L, 
            ((Number) session
            .createSQLQuery("select quantity from product where id = :id")
            .setParameter("id", product.getId())
            .uniqueResult())
            .longValue()
        );
    } catch (Exception e) {
        fail(e.getMessage());
    }
});

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

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

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

Гибернация-это средство, а не цель. Уровень доступа к данным требует как чтения, так и записи, и ни обычный JDBC, ни Hibernate не являются универсальными решениями для всех. Стек знаний о данных гораздо более подходит для получения большей части ваших запросов на чтение данных и написания инструкций DML.

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

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

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

Код доступен на GitHub .