1. Общ преглед
Едно от предимствата на слоевете за абстракция на база данни, като ORM (обектно-релационно картографиране) рамки, е способността им да кешират прозрачно данните, извлечени от основното хранилище. Това помага да се елиминират разходите за достъп до база данни за често достъпвани данни.
Повишаването на производителността може да бъде значително, ако съотношенията на четене / запис на кеширано съдържание са високи, особено за обекти, които се състоят от големи обектни графики.
В тази статия изследваме хибернация на кеш от второ ниво.
Обясняваме някои основни понятия и както винаги илюстрираме всичко с прости примери. Използваме JPA и се връщаме към Hibernate native API само за онези функции, които не са стандартизирани в JPA.
2. Какво представлява кешът от второ ниво?
Както повечето други напълно оборудвани ORM рамки, Hibernate има концепцията за кеш от първо ниво. Това е кеш с обхват на сесия, който гарантира, че всеки екземпляр на обект се зарежда само веднъж в постоянния контекст.
След като сесията бъде затворена, кешът от първо ниво също се прекратява. Това всъщност е желателно, тъй като позволява едновременните сесии да работят с екземпляри на обекти изолирано един от друг.
От друга страна, кешът от второ ниво е SessionFactory -scoped, което означава, че се споделя от всички сесии, създадени с една и съща фабрична сесия. Когато екземпляр на обект се търси по неговия идентификатор (или чрез логика на приложението, или от хибернация вътрешно, напр. Когато зарежда асоциации към този обект от други обекти) и ако е разрешено кеширане на второ ниво за този обект, се случва следното:
- Ако даден екземпляр вече присъства в кеша на първо ниво, той се връща от там
- Ако екземпляр не е намерен в кеша на първо ниво и съответното състояние на екземпляра се кешира в кеша на второ ниво, тогава данните се извличат оттам и екземпляр се сглобява и връща
- В противен случай необходимите данни се зареждат от базата данни и екземпляр се сглобява и връща
След като екземплярът се съхранява в контекста на постоянство (кеш от първо ниво), той се връща оттам във всички следващи повиквания в рамките на същата сесия, докато сесията бъде затворена или екземплярът бъде изгонен ръчно от контекста на постоянство. Също така зареденото състояние на екземпляр се съхранява в L2 кеш, ако вече не е било там.
3. Регионална фабрика
Hibernate кеширането от второ ниво е проектирано да не знае за реалния използван доставчик на кеш. Hibernate трябва само да бъде снабден с реализация на интерфейса org.hibernate.cache.spi.RegionFactory, който капсулира всички подробности, специфични за действителните доставчици на кеш. По принцип той действа като мост между Hibernate и доставчиците на кеш.
В тази статия използваме Ehcache като доставчик на кеш , който е зрял и широко използван кеш. Можете да изберете всеки друг доставчик, разбира се, стига да има внедряване на RegionFactory за него.
Добавяме фабричната реализация на региона Ehcache към пътя на класа със следната зависимост на Maven:
org.hibernate hibernate-ehcache 5.2.2.Final
Погледнете тук за най-новата версия на hibernate-ehcache . Уверете се обаче, че версията на hibernate-ehcache е равна на версията на Hibernate, която използвате във вашия проект, например ако използвате hibernate-ehcache 5.2.2. Финална, както в този пример, тогава версията на Hibernate също трябва да бъде 5.2.2. Финал .
В зимен сън-ehcache артефакт има зависимостта от самото изпълнение Ehcache, което е по този начин transitively включени в CLASSPATH, както добре.
4. Активиране на кеширане от второ ниво
Със следните две свойства казваме на Hibernate, че L2 кеширането е активирано и му даваме името на регионалния фабричен клас:
hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
Например в persistence.xml ще изглежда така:
... ...
За да деактивирате кеширането от второ ниво (например за целите на отстраняване на грешки), просто задайте свойството hibernate.cache.use_second_level_cache на false.
5. Осъществяване на обект, който може да се кешира
За да направим обект, отговарящ на изискванията за кеширане от второ ниво , ние го анотираме със специфична за Hibernate @ org.hibernate.annotations.Cache анотация и посочваме стратегия за съвпадение на кеша.
Някои разработчици считат, че е добра конвенция да добавят и стандартната анотация @ javax.persistence.Cacheable (въпреки че не се изисква от Hibernate), така че изпълнението на клас на обект може да изглежда така:
@Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; @Column(name = "NAME") private String name; // getters and setters }
За всеки клас обект Hibernate ще използва отделен регион на кеша, за да съхранява състоянието на екземпляри за този клас. Името на региона е напълно квалифицирано име на клас.
Например, екземплярите на Foo се съхраняват в кеш на име com.baeldung.hibernate.cache.model.Foo в Ehcache.
За да проверим дали кеширането работи, можем да напишем бърз тест като този:
Foo foo = new Foo(); fooService.create(foo); fooService.findOne(foo.getId()); int size = CacheManager.ALL_CACHE_MANAGERS.get(0) .getCache("com.baeldung.hibernate.cache.model.Foo").getSize(); assertThat(size, greaterThan(0));
Тук използваме директно API на Ehcache, за да проверим дали com.baeldung.hibernate.cache.model.Foo кешът не е празен, след като заредим екземпляр на Foo .
Можете също така да активирате регистрирането на SQL, генериран от Hibernate, и да извикате fooService.findOne (foo.getId ()) няколко пъти в теста, за да проверите дали операторът select за зареждане на Foo се отпечатва само веднъж (първия път), което означава, че в следващите извиква екземпляра на обекта се извлича от кеша.
6. Стратегия за кеш паралелност
Въз основа на случаи на употреба ние сме свободни да изберем една от следните стратегии за едновременност на кеша:
- READ_ONLY : Използва се само за обекти, които никога не се променят (изключение се извежда, ако се прави опит за актуализиране на такъв обект). Той е много прост и изпълним. Много подходящ за някои статични референтни данни, които не се променят
- NONSTRICT_READ_WRITE : Кешът се актуализира след извършване на транзакция, която е променила засегнатите данни. По този начин не се гарантира силна последователност и има малък времеви прозорец, в който от кеша могат да се получат остарели данни. Този вид стратегия е подходящ за случаи на употреба, които могат да понасят евентуална последователност
- READ_WRITE: This strategy guarantees strong consistency which it achieves by using ‘soft' locks: When a cached entity is updated, a soft lock is stored in the cache for that entity as well, which is released after the transaction is committed. All concurrent transactions that access soft-locked entries will fetch the corresponding data directly from database
- TRANSACTIONAL: Cache changes are done in distributed XA transactions. A change in a cached entity is either committed or rolled back in both database and cache in the same XA transaction
7. Cache Management
If expiration and eviction policies are not defined, the cache could grow indefinitely and eventually consume all of available memory. In most cases, Hibernate leaves cache management duties like these to cache providers, as they are indeed specific to each cache implementation.
For example, we could define the following Ehcache configuration to limit the maximum number of cached Foo instances to 1000:
8. Collection Cache
Collections are not cached by default, and we need to explicitly mark them as cacheable. For example:
@Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { ... @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) @OneToMany private Collection bars; // getters and setters }
9. Internal Representation of Cached State
Entities are not stored in second-level cache as Java instances, but rather in their disassembled (hydrated) state:
- Id (primary key) is not stored (it is stored as part of the cache key)
- Transient properties are not stored
- Collections are not stored (see below for more details)
- Non-association property values are stored in their original form
- Only id (foreign key) is stored for ToOne associations
This depicts general Hibernate second-level cache design in which cache model reflects the underlying relational model, which is space-efficient and makes it easy to keep the two synchronized.
9.1. Internal Representation of Cached Collections
We already mentioned that we have to explicitly indicate that a collection (OneToMany or ManyToMany association) is cacheable, otherwise it is not cached.
Actually, Hibernate stores collections in separate cache regions, one for each collection. The region name is a fully qualified class name plus the name of collection property, for example: com.baeldung.hibernate.cache.model.Foo.bars. This gives us the flexibility to define separate cache parameters for collections, e.g. eviction/expiration policy.
Also, it is important to mention that only ids of entities contained in a collection are cached for each collection entry, which means that in most cases it is a good idea to make the contained entities cacheable as well.
10. Cache Invalidation for HQL DML-Style Queries and Native Queries
When it comes to DML-style HQL (insert, update and delete HQL statements), Hibernate is able to determine which entities are affected by such operations:
entityManager.createQuery("update Foo set … where …").executeUpdate();
In this case all Foo instances are evicted from L2 cache, while other cached content remains unchanged.
However, when it comes to native SQL DML statements, Hibernate cannot guess what is being updated, so it invalidates the entire second level cache:
session.createNativeQuery("update FOO set … where …").executeUpdate();
This is probably not what you want! The solution is to tell Hibernate which entities are affected by native DML statements, so that it can evict only entries related to Foo entities:
Query nativeQuery = entityManager.createNativeQuery("update FOO set ... where ..."); nativeQuery.unwrap(org.hibernate.SQLQuery.class).addSynchronizedEntityClass(Foo.class); nativeQuery.executeUpdate();
We have too fall back to Hibernate native SQLQuery API, as this feature is not (yet) defined in JPA.
Note that the above applies only to DML statements (insert, update, delete and native function/procedure calls). Native select queries do not invalidate cache.
11. Query Cache
Results of HQL queries can also be cached. This is useful if you frequently execute a query on entities that rarely change.
To enable query cache, set the value of hibernate.cache.use_query_cache property to true:
hibernate.cache.use_query_cache=true
Then, for each query you have to explicitly indicate that the query is cacheable (via an org.hibernate.cacheable query hint):
entityManager.createQuery("select f from Foo f") .setHint("org.hibernate.cacheable", true) .getResultList();
11.1. Query Cache Best Practices
Here are a some guidelines and best practices related to query caching:
- As is case with collections, only ids of entities returned as a result of a cacheable query are cached, so it is strongly recommended that second-level cache is enabled for such entities.
- There is one cache entry per each combination of query parameter values (bind variables) for each query, so queries for which you expect lots of different combinations of parameter values are not good candidates for caching.
- Queries that involve entity classes for which there are frequent changes in the database are not good candidates for caching either, because they will be invalidated whenever there is a change related to any of the entity classed participating in the query, regardless whether the changed instances are cached as part of the query result or not.
- By default, all query cache results are stored in org.hibernate.cache.internal.StandardQueryCache region. As with entity/collection caching, you can customize cache parameters for this region to define eviction and expiration policies according to your needs. For each query you can also specify a custom region name in order to provide different settings for different queries.
- For all tables that are queried as part of cacheable queries, Hibernate keeps last update timestamps in a separate region named org.hibernate.cache.spi.UpdateTimestampsCache. Being aware of this region is very important if you use query caching, because Hibernate uses it to verify that cached query results are not stale. The entries in this cache must not be evicted/expired as long as there are cached query results for the corresponding tables in query results regions. It is best to turn off automatic eviction and expiration for this cache region, as it does not consume lots of memory anyway.
12. Conclusion
В тази статия разгледахме как да настроим Hibernate кеш от второ ниво. Видяхме, че е доста лесно да се конфигурира и използва, тъй като Hibernate прави всичко тежко зад кулисите, което прави използването на кеша на второ ниво прозрачно за бизнес логиката на приложенията.
Реализацията на този урок за хибернация от второ ниво на кеша е достъпна на Github. Това е проект, базиран на Maven, така че трябва да е лесно да се импортира и да се изпълнява както е.