Кэш первого уровня JPA и Hibernate

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Введение

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

В терминологии JPA кэш первого уровня называется Persistence Context, и он представлен интерфейсом EntityManager. В Hibernate кэш первого уровня представлен интерфейсом Session, который является расширением к JPA EntityManager.

Состояния сущностей JPA и связанные с ними методы перехода 

Сущность JPA может находиться в одном из следующих состояний:

  • Новая (Transient)

  • Управляемая ( Associated)

  • Отсоединенная (Dissociated)

  • Удалена (Deleted)

Чтобы изменить состояние сущности, вы можете использовать методы persist, merge или remove JPA EntityManager, как показано на следующей диаграмме:

Когда вы вызываете метод persist, состояние сущности меняется с New (Новая) на Managed (Управляемая).

И при вызове метода find состояние сущности также становится Managed (Управляемая).

После закрытия EntityManager или вызова метода evict состояние сущности становится Detached (Отсоединенная).

Когда сущность передается в метод remove JPA EntityManager, состояние сущности становится Removed (Удаленная).

Имплементация кэша первого уровня в Hibernate

Внутри Hibernate сохраняет сущности в следующей карте:

Map<EntityUniqueKey, Object> entitiesByUniqueKey = new HashMap<>(INIT_COLL_SIZE);

А EntityUniqueKey определяется следующим образом:

public class EntityUniqueKey implements Serializable {
 
    private final String entityName;
     
    private final String uniqueKeyName;
     
    private final Object key;
     
    private final Type keyType;
     
    ...
 
    @Override
    public boolean equals(Object other) {
        EntityUniqueKey that = (EntityUniqueKey) other;
         
        return that != null &&
            that.entityName.equals(entityName) &&
            that.uniqueKeyName.equals(uniqueKeyName) &&
            keyType.isEqual(that.key, key);
    }
     
    ...
}

Когда состояние сущности становится Managed, это означает, что оно хранится в этой Java Map entitiesByUniqueKey.

Итак, в JPA и Hibernate кэш первого уровня - это Java Map, в котором ключ Map представлен объектом, инкапсулирующим имя сущности и ее идентификатор, а значение Map - это сам объект сущности.

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

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

Транзакционная отложенная запись 

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

Как уже объяснялось, методы persist, merge и remove JPA EntityManager изменяют состояние данной сущности. Однако состояние сущности не синхронизируется каждый раз, когда вызывается метод EntityManager. На самом деле, изменения состояния синхронизируются только при выполнении метода flush EntityManager.

Эта стратегия синхронизации кэша называется write-behind (отложенная запись) и выглядит следующим образом:

Преимущество использования стратегии write-behind заключается в том, что мы можем пакетно обрабатывать несколько объектов при очистке кэша первого уровня.

Стратегия write-behind на самом деле очень распространена. Процессор также имеет кэши первого, второго и третьего уровней. И при изменении реестра его состояние не синхронизируется с основной памятью, пока не будет выполнен сброс (flush).

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

Повторяющиеся чтения на уровне приложения

Когда вы получаете сущность JPA, либо напрямую:

Post post = entityManager.find(Post.class, 1L);

Или через запрос:

Post post = entityManager.createQuery("""
    select p
    from Post p
    where p.id = :id
    """, Post.class)
.setParameter("id", 1L)
.getSingleResult();

Будет вызвано событие Hibernate LoadEntityEvent. Событие LoadEntityEvent обрабатывается DefaultLoadEventListener, который загрузит сущность следующим образом:

Сначала Hibernate проверяет, хранится ли сущность в кэше первого уровня, и если да, то возвращается текущая управляемая ссылка на нее.

В случае, когда сущность JPA не найдена в кэше первого уровня, Hibernate проверяет кэш второго уровня, если этот кэш включен.

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

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

Когда сущность загружается из базы данных, Hibernate берет JDBC ResultSet и преобразует его в Java Object[], который известен как состояние загруженной сущности. Загруженное состояние хранится в кэше первого уровня вместе с управляемой сущностью, как показано на следующей диаграмме:

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

По этой причине загрузка сущности занимает больше памяти, чем сам объект Java-сущности, так как необходимо хранить еще и загруженное состояние. При сбросе (flush) JPA Persistence Context загруженное состояние будет использовано механизмом "грязной" проверки (dirty checking), чтобы определить, изменилась ли сущность с момента ее первой загрузки. Если она изменилась, будет сгенерирована команда SQL UPDATE.

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

Заключение

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

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

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


Материал подготовлен в рамках курса «Java Developer. Professional».

Всех желающих приглашаем на бесплатное demo-занятие «Применение kafka для связи микросервисов на Java Spring Boot». Продолжаем разрабатывать систему получения курса валюты. Познакомимся с kafka и организуем с ее помощью взаимосвязь пары микросервисов.
>> РЕГИСТРАЦИЯ

Источник: https://habr.com/ru/company/otus/blog/596087/


Интересные статьи

Интересные статьи

Материал подготовлен Никитой Кузнецовым, старшим разработчиком компании «МегаФон» и преподавателем курса “Go-Разработчик Pro” в Skillbox: «Упрощай! Вот девиз, который, как мне кажется, могли бы ис...
Компания «Деловой разговор» — Титановый партнер 3СХ — осуществила расширенную интеграцию IP-АТС 3CX с Битрикс 24. Ранее уже существовали отдельные модули, решающие конкретные задачи, напр...
Субботний вечер омрачен скандалом - сайт не работает, провайдер негодяй, админы - не специалисты, а сервера - решето. Вызов принят, или почему при всей нелюбви к 1С-Битри...
Делимся подборкой для специалистов, ученых и студентов технических специальностей, плюс — их младших коллег. Рассказываем о ближайших тематических мероприятиях и конферен...
Модели наушников с костной проводимостью прошлых сезонов были в общем и целом хороши по всем параметрам. Единственные нарекания, которые возникали, касались качество передачи звука, что объясняло...