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

Как в режиме гибернации хранятся записи кэша второго уровня

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

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

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

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

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

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


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

По этой причине вам необходимо настроить соответствующий сторонний RegionFactory , такой как Ehcache или Infinispan:


RegionFactory определяет поставщика реализации кэша второго уровня, и hibernate.cache.region.factory_class конфигурация обязательна, как только для свойства hibernate.cache.use_second_level_cache установлено значение true .

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

@Entity
@org.hibernate.annotations.Cache(usage = 
    CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)

JPA также определяет аннотацию @Cacheable , но она не поддерживает настройку стратегии параллелизма на уровне сущности.

Поток загрузки объекта

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

Object entity = loadFromSessionCache( event, 
    keyToLoad, options );
if ( entity == REMOVED_ENTITY_MARKER ) {
    LOG.debug("Load request found matching entity 
        in context, but it is scheduled for removal;
        returning null" );
    return null;
}
if ( entity == INCONSISTENT_RTN_CLASS_MARKER ) {
    LOG.debug("Load request found matching entity 
        in context, but the matched entity was of
        an inconsistent return type;
        returning null"
    );
    return null;
}
if ( entity != null ) {
    if ( traceEnabled ) {
        LOG.tracev("Resolved object in "
            + "session cache: {0}",
            MessageHelper.infoString( persister,
                event.getEntityId(),
                event.getSession().getFactory() )
        );
    }
    return entity;
}

entity = loadFromSecondLevelCache( event, 
    persister, options );
if ( entity != null ) {
    if ( traceEnabled ) {
        LOG.tracev("Resolved object in "
            + "second-level cache: {0}",
            MessageHelper.infoString( persister,
                event.getEntityId(),
                event.getSession().getFactory() )
        );
    }
}
else {
    if ( traceEnabled ) {
        LOG.tracev("Object not resolved in "
            + "any cache: {0}",
            MessageHelper.infoString( persister,
                event.getEntityId(),
                event.getSession().getFactory() )
        );
    }
    entity = loadFromDatasource( event, persister, 
        keyToLoad, options );
}

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

Внутренние компоненты кэша второго уровня

Каждая сущность хранится как Запись в кэше , и состояние сущности гидратировано используется для создания значения записи в кэше.

Гидратация

В номенклатуре гибернации гидратация – это когда набор результатов JDBC преобразуется в массив необработанных значений:

final Object[] values = persister.hydrate(
    rs, id, object, 
    rootPersister, cols, eagerPropertyFetch, session
);

Гидратированное состояние сохраняется в текущем контексте сохранения как объект EntityEntry , который инкапсулировал моментальный снимок сущности во время загрузки. Затем гидратированное состояние используется:

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

Обратная операция называется обезвоживание , и она копирует состояние сущности в оператор ВСТАВКА или ОБНОВЛЕНИЕ .

Элементы кэша второго уровня

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

final CacheEntry entry = persister.buildCacheEntry( 
    entity, hydratedState, version, session );

Гидратированное состояние разбирается перед сохранением в записи Кэша :

this.disassembledState = TypeHelper.disassemble(
    state, persister.getPropertyTypes(),
    persister.isLazyPropertiesCacheable() 
        ? null : persister.getPropertyLaziness(),
    session, owner
);

Начиная со следующей диаграммы модели сущности:

Мы вставим следующие объекты:

Post post = new Post();
post.setName("Hibernate Master Class");

post.addDetails(new PostDetails());
post.addComment(new Comment("Good post!"));
post.addComment(new Comment("Nice post!"));

session.persist(post);

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

Элемент кэша сущности Post

Сообщение сущность имеет связь “один ко многим” с Комментарием сущностью и обратную связь “один к одному” с Деталями сообщения :

@OneToMany(cascade = CascadeType.ALL, 
    mappedBy = "post")
private List comments = new ArrayList<>();

@OneToOne(cascade = CascadeType.ALL, 
    mappedBy = "post", optional = true)
private PostDetails details;

При извлечении Записи сущности:

Post post = (Post) session.get(Post.class, 1L);

Связанный элемент кэша выглядит следующим образом:

key = {org.hibernate.cache.spi.CacheKey@3855}
    key = {java.lang.Long@3860} "1"
    type = {org.hibernate.type.LongType@3861} 
    entityOrRoleName = {java.lang.String@3862} "com.vladmihalcea.hibernate.masterclass.laboratory.cache.SecondLevelCacheTest$Post"
    tenantId = null
    hashCode = 31
value = {org.hibernate.cache.spi.entry.StandardCacheEntryImpl@3856}
    disassembledState = {java.io.Serializable[3]@3864} 
        0 = {java.lang.Long@3860} "1"
        1 = {java.lang.String@3865} "Hibernate Master Class"
    subclass = {java.lang.String@3862} "com.vladmihalcea.hibernate.masterclass.laboratory.cache.SecondLevelCacheTest$Post"
    lazyPropertiesAreUnfetched = false
    version = null

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

Значение кэша Записи записи состоит из столбца имя и идентификатор , который задается ассоциацией один ко многим Комментарий.

Ни один ко многим , ни обратные один к одному ассоциации не встроены в запись Post |/Кэша .

Элемент кэша сущности Сведений о записи

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

@OneToOne
@MapsId
private Post post;

При получении Сведений о записи сущности:

PostDetails postDetails = 
    (PostDetails) session.get(PostDetails.class, 1L);

Кэш второго уровня генерирует следующий элемент кэша:

key = {org.hibernate.cache.spi.CacheKey@3927}
    key = {java.lang.Long@3897} "1"
    type = {org.hibernate.type.LongType@3898} 
    entityOrRoleName = {java.lang.String@3932} "com.vladmihalcea.hibernate.masterclass.laboratory.cache.SecondLevelCacheTest$PostDetails"
    tenantId = null
    hashCode = 31
value = {org.hibernate.cache.spi.entry.StandardCacheEntryImpl@3928}
    disassembledState = {java.io.Serializable[2]@3933} 
        0 = {java.sql.Timestamp@3935} "2015-04-06 15:36:13.626"
    subclass = {java.lang.String@3932} "com.vladmihalcea.hibernate.masterclass.laboratory.cache.SecondLevelCacheTest$PostDetails"
    lazyPropertiesAreUnfetched = false
    version = null

Разобранное состояние содержит только свойство , созданное на сущности, поскольку идентификатор сущности встроен в CacheKey .

Элемент кэша сущности комментария

Комментарий сущность имеет связь “многие к одному” с Сообщением :

@ManyToOne
private Post post;

Когда мы получаем Комментарий сущность:

Comment comments = 
    (Comment) session.get(Comment.class, 1L);

Hibernate создает следующий элемент кэша второго уровня:

key = {org.hibernate.cache.spi.CacheKey@3857}
    key = {java.lang.Long@3864} "2"
    type = {org.hibernate.type.LongType@3865} 
    entityOrRoleName = {java.lang.String@3863} "com.vladmihalcea.hibernate.masterclass.laboratory.cache.SecondLevelCacheTest$Comment"
    tenantId = null
    hashCode = 62
value = {org.hibernate.cache.spi.entry.StandardCacheEntryImpl@3858}
    disassembledState = {java.io.Serializable[2]@3862} 
        0 = {java.lang.Long@3867} "1"
        1 = {java.lang.String@3868} "Good post!"
    subclass = {java.lang.String@3863} "com.vladmihalcea.hibernate.masterclass.laboratory.cache.SecondLevelCacheTest$Comment"
    lazyPropertiesAreUnfetched = false
    version = null

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

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

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

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

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