Hibernasi Cache Tingkat Kedua

1. Ikhtisar

Salah satu keuntungan dari lapisan abstraksi database seperti kerangka kerja ORM (pemetaan relasional objek) adalah kemampuannya untuk secara transparan menyimpan data cache yang diambil dari penyimpanan yang mendasarinya. Ini membantu menghilangkan biaya akses database untuk data yang sering diakses.

Peningkatan kinerja dapat menjadi signifikan jika rasio baca / tulis konten yang disimpan dalam cache tinggi, terutama untuk entitas yang terdiri dari grafik objek besar.

Pada artikel ini kami menjelajahi cache tingkat kedua Hibernate.

Kami menjelaskan beberapa konsep dasar dan seperti biasa kami mengilustrasikan semuanya dengan contoh sederhana. Kami menggunakan JPA dan kembali ke API asli Hibernate hanya untuk fitur-fitur yang tidak terstandarisasi di JPA.

2. Apa Itu Tembolok Tingkat Kedua?

Seperti kebanyakan kerangka kerja ORM lengkap lainnya, Hibernate memiliki konsep cache tingkat pertama. Ini adalah cache cakupan sesi yang memastikan bahwa setiap instance entitas dimuat hanya sekali dalam konteks yang persisten.

Setelah sesi ditutup, cache tingkat pertama juga dihentikan. Ini sebenarnya diinginkan, karena memungkinkan sesi bersamaan untuk bekerja dengan instance entitas secara terpisah satu sama lain.

Di sisi lain, cache level kedua adalah SessionFactory -scoped, artinya cache dibagikan oleh semua sesi yang dibuat dengan pabrik sesi yang sama. Ketika sebuah instance entitas dicari oleh id-nya (baik dengan logika aplikasi atau dengan Hibernate secara internal, misalnya ketika memuat asosiasi ke entitas itu dari entitas lain), dan jika cache tingkat kedua diaktifkan untuk entitas itu, hal berikut akan terjadi:

  • Jika sebuah instance sudah ada di cache tingkat pertama, ia dikembalikan dari sana
  • Jika sebuah instance tidak ditemukan di cache tingkat pertama, dan status instance terkait di-cache di cache tingkat kedua, maka data akan diambil dari sana dan sebuah instance dirakit dan dikembalikan
  • Jika tidak, data yang diperlukan akan dimuat dari database dan sebuah instance dirakit dan dikembalikan

Setelah instance disimpan dalam konteks persistensi (cache tingkat pertama), instance tersebut dikembalikan dari sana di semua panggilan berikutnya dalam sesi yang sama hingga sesi tersebut ditutup atau instance tersebut dikeluarkan secara manual dari konteks persistensi. Selain itu, status instance yang dimuat disimpan dalam cache L2 jika belum ada.

3. Pabrik Wilayah

Caching tingkat kedua Hibernate dirancang untuk tidak mengetahui penyedia cache sebenarnya yang digunakan. Hibernate hanya perlu disediakan dengan implementasi antarmuka org.hibernate.cache.spi.RegionFactory yang merangkum semua detail khusus untuk penyedia cache sebenarnya. Pada dasarnya, ini bertindak sebagai jembatan antara Hibernate dan penyedia cache.

Dalam artikel ini kami menggunakan Ehcache sebagai penyedia cache , yang merupakan cache yang matang dan banyak digunakan. Anda tentu saja dapat memilih penyedia lain, selama ada implementasi RegionFactory untuknya.

Kami menambahkan implementasi pabrik region Ehcache ke classpath dengan dependensi Maven berikut:

 org.hibernate hibernate-ehcache 5.2.2.Final 

Lihat di sini untuk versi terbaru hibernate-ehcache . Namun, pastikan bahwa versi hibernate-ehcache sama dengan versi Hibernate yang Anda gunakan dalam proyek Anda, misalnya jika Anda menggunakan hibernate-ehcache 5.2.2. Terakhir seperti contoh ini, maka versi Hibernate juga harus 5.2.2. Final .

The hibernate-ehcache artefak memiliki ketergantungan pada pelaksanaan EHCache itu sendiri, yang dengan demikian transitif termasuk dalam classpath juga.

4. Mengaktifkan Caching Tingkat Kedua

Dengan dua properti berikut kami memberi tahu Hibernate bahwa cache L2 diaktifkan dan kami memberinya nama kelas pabrik wilayah:

hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 

Misalnya, di persistence.xml akan terlihat seperti ini:

 ...   ... 

Untuk menonaktifkan cache tingkat kedua (untuk tujuan debugging misalnya), cukup setel properti hibernate.cache.use_second_level_cache ke false.

5. Membuat Entitas Dapat Di-cache

Untuk membuat entitas memenuhi syarat untuk cache tingkat kedua , kami menganotasinya dengan Hibernate specific @ org.hibernate.annotations.Cache anotasi dan menentukan strategi konkurensi cache.

Beberapa pengembang menganggap bahwa itu adalah konvensi yang baik untuk menambahkan anotasi @ javax.persistence.Cacheable standar juga (meskipun tidak diharuskan oleh Hibernate), jadi implementasi kelas entitas mungkin terlihat seperti ini:

@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 }

Untuk setiap kelas entitas, Hibernate akan menggunakan wilayah cache terpisah untuk menyimpan status instance untuk kelas itu. Nama wilayah adalah nama kelas yang sepenuhnya memenuhi syarat.

Misalnya, instance Foo disimpan dalam cache bernama com.baeldung.hibernate.cache.model.Foo di Ehcache.

Untuk memverifikasi bahwa caching berfungsi, kami dapat menulis tes cepat seperti ini:

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));

Di sini kami menggunakan Ehcache API secara langsung untuk memverifikasi bahwa cache com.baeldung.hibernate.cache.model.Foo tidak kosong setelah kami memuat instance Foo .

Anda juga dapat mengaktifkan pencatatan SQL yang dihasilkan oleh Hibernate dan menjalankan fooService.findOne (foo.getId ()) beberapa kali dalam pengujian untuk memverifikasi bahwa pernyataan pemilihan untuk memuat Foo dicetak hanya sekali (pertama kali), yang berarti bahwa selanjutnya panggilan instance entitas diambil dari cache.

6. Strategi Konkurensi Cache

Berdasarkan kasus penggunaan, kami bebas memilih salah satu dari strategi konkurensi cache berikut:

  • READ_ONLY : Digunakan hanya untuk entitas yang tidak pernah berubah (pengecualian dilemparkan jika upaya untuk memperbarui entitas tersebut dilakukan). Ini sangat sederhana dan bagus. Sangat cocok untuk beberapa data referensi statis yang tidak berubah
  • NONSTRICT_READ_WRITE : Cache diperbarui setelah transaksi yang mengubah data yang terpengaruh telah dilakukan. Karenanya, konsistensi yang kuat tidak dijamin dan ada jendela waktu kecil di mana data usang dapat diperoleh dari cache. Strategi semacam ini cocok untuk kasus penggunaan yang dapat mentolerir konsistensi akhir
  • 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

Pada artikel ini kami melihat cara mengatur cache tingkat kedua Hibernate. Kami melihat bahwa ini cukup mudah untuk dikonfigurasi dan digunakan, karena Hibernate melakukan semua pekerjaan berat di belakang layar yang membuat pemanfaatan cache tingkat kedua menjadi transparan bagi logika bisnis aplikasi.

Implementasi dari Tutorial Cache Tingkat Kedua Hibernasi ini tersedia di Github. Ini adalah proyek berbasis Maven, jadi semestinya mudah untuk mengimpor dan menjalankan apa adanya.