Вступление
Преимущество использования уровня абстракции доступа к базе данных состоит в том, что кэширование может быть реализовано прозрачно, без утечки в код бизнес-логики . Hibernate Persistence Context действует как транзакционный кэш с обратной записью , переводя переходы состояний сущностей в операторы DML .
Постоянный контекст действует как хранилище логических транзакций , и каждый экземпляр сущности может иметь не более одной управляемой ссылки. Независимо от того, сколько раз мы пытаемся загрузить один и тот же объект, сеанс Hibernate всегда будет возвращать одну и ту же ссылку на объект. Такое поведение обычно изображается как кэш первого уровня .
Hibernate Persistence Context сам по себе не является решением для кэширования и служит не для повышения производительности операций чтения приложений, а для другой цели. Поскольку сеанс Hibernate привязан к текущей запущенной логической транзакции, после завершения транзакции сеанс уничтожается.
Кеш второго уровня
Правильное решение для кэширования должно охватывать несколько сеансов Hibernate, и именно поэтому Hibernate также поддерживает дополнительный кэш второго уровня . Кэш второго уровня привязан к жизненному циклу SessionFactory , поэтому он уничтожается только при закрытии SessionFactory (в частности, при закрытии приложения). Кэш второго уровня в основном ориентирован на сущности, хотя также поддерживает дополнительное решение для кэширования запросов.
По умолчанию кэш второго уровня отключен, и для его активации необходимо установить следующие свойства Hibernate :
1
2
3
4
|
properties.put( "hibernate.cache.use_second_level_cache" , Boolean.TRUE.toString()); properties.put( "hibernate.cache.region.factory_class" , "org.hibernate.cache.ehcache.EhCacheRegionFactory" ); |
RegionFactory определяет поставщика реализации кэша второго уровня, и конфигурация hibernate.cache.region.factory_class является обязательной, если для свойства hibernate.cache.use_second_level_cache установлено значение true .
Чтобы включить кэширование на уровне сущностей, нам нужно аннотировать наши кэшируемые сущности следующим образом:
1
2
3
|
@Entity @org .hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) |
JPA также определяет аннотацию @Cacheable , но не поддерживает настройку стратегии параллелизма на уровне объекта.
Поток загрузки объекта
Всякий раз, когда объект должен быть загружен, запускается LoadEevent, и DefaultLoadEventListener обрабатывает его следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
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 ); } |
Сеанс всегда проверяется первым, поскольку он может уже содержать экземпляр управляемого объекта. Кэш второго уровня проверяется перед попаданием в базу данных, поэтому его основная цель — уменьшить количество обращений к базе данных.
Внутренние элементы кэша второго уровня
Каждый объект сохраняется как CacheEntry , а гидратированное состояние объекта используется для создания значения записи в кэше.
гидратация
В номенклатуре Hibernate гидратация — это когда JDBC ResultSet преобразуется в массив необработанных значений:
1
2
3
4
|
final Object[] values = persister.hydrate( rs, id, object, rootPersister, cols, eagerPropertyFetch, session ); |
Гидратированное состояние сохраняется в текущем запущенном контексте постоянства как объект EntityEntry , который инкапсулировал моментальный снимок объекта во время загрузки. Гидратированное состояние затем используется:
- механизм грязной проверки по умолчанию , который сравнивает текущие данные объекта со снимком времени загрузки
- кэш второго уровня, записи которого создаются на основе моментального снимка объекта времени загрузки
Обратная операция называется дегидратацией и копирует состояние объекта в оператор INSERT или UPDATE .
Элементы кэша второго уровня
Хотя Hibernate позволяет нам манипулировать графами сущностей, кэш второго уровня использует вместо этого разобранное гидратированное состояние :
1
2
|
final CacheEntry entry = persister.buildCacheEntry( entity, hydratedState, version, session ); |
Гидратированное состояние разбирается перед сохранением в CacheEntry :
1
2
3
4
5
6
|
this .disassembledState = TypeHelper.disassemble( state, persister.getPropertyTypes(), persister.isLazyPropertiesCacheable() ? null : persister.getPropertyLaziness(), session, owner ); |
Начиная со следующей диаграммы модели объекта:
Мы вставим следующие объекты:
1
2
3
4
5
6
7
8
|
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
Сущность Post имеет однозначную связь с сущностью Comment и обратную однозначную связь с PostDetails :
1
2
3
4
5
6
7
|
@OneToMany (cascade = CascadeType.ALL, mappedBy = "post" ) private List<Comment> comments = new ArrayList<>(); @OneToOne (cascade = CascadeType.ALL, mappedBy = "post" , optional = true ) private PostDetails details; |
При получении объекта Post :
1
|
Post post = (Post) session.get(Post. class , 1L); |
Связанный элемент кэша выглядит так:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
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 |
CacheKey содержит идентификатор объекта, а CacheEntry содержит разобранное гидратированное состояние объекта.
Значение кэша записей записей состоит из столбца имени и идентификатора , который устанавливается ассоциацией комментариев « один ко многим» .
Ни взаимно-однозначные, ни обратные взаимно-однозначные ассоциации не встроены в Post CacheEntry .
Элемент кэша сущности PostDetails
Первичный ключ сущности PostDetails ссылается на связанный первичный ключ сущности Post , и поэтому он имеет непосредственную связь с сущностью Post .
1
2
3
4
|
@OneToOne @JoinColumn (name = "id" ) @MapsId private Post post; |
При получении сущности PostDetails :
1
2
|
PostDetails postDetails = (PostDetails) session.get(PostDetails. class , 1L); |
Кэш второго уровня генерирует следующий элемент кеша:
01
02
03
04
05
06
07
08
09
10
11
12
|
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 |
В дизассемблированном состоянии содержится только свойство объекта selectedOn , поскольку идентификатор объекта встроен в CacheKey .
Элемент кэша сущности Comment
Сущность Comment имеет связь « многие к одному» с сообщением :
1
2
|
@ManyToOne private Post post; |
Когда мы получаем сущность Comment :
1
2
|
Comment comments = (Comment) session.get(Comment. class , 1L); |
Hibernate генерирует следующий элемент кэша второго уровня:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
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 .
Ссылка: | Как Hibernate хранит записи кэша второго уровня от нашего партнера по JCG Влада Михалча в блоге Влада Михалча . |