Hibernate Second Level Cache

1. Überblick

Einer der Vorteile von Datenbankabstraktionsschichten wie ORM-Frameworks (Object Relational Mapping) ist die ** Fähigkeit, Daten transparent zwischenzuspeichern, die aus dem zugrunde liegenden Speicher abgerufen werden. Dadurch werden Datenbankzugriffskosten für häufig aufgerufene Daten vermieden.

Leistungsgewinne können erheblich sein, wenn die Lese/Schreib-Verhältnisse zwischengespeicherter Inhalte hoch sind, insbesondere für Entitäten, die aus großen Objektgraphen bestehen.

  • In diesem Artikel wird der Cache der zweiten Ebene im Hibernate-Modus untersucht. **

Wir erklären einige grundlegende Konzepte und veranschaulichen alles wie immer mit einfachen Beispielen. Wir verwenden JPA und greifen auf die native Hibernate-API nur für diejenigen Funktionen zurück, die in JPA nicht standardisiert sind.

2. Was ist ein Second-Level-Cache?

Wie die meisten anderen voll ausgestatteten ORM-Frameworks hat Hibernate das Konzept des First-Level-Caches. Es ist ein Cache mit Sitzungsbereich, der sicherstellt, dass jede Entitätsinstanz nur einmal im permanenten Kontext geladen wird.

Sobald die Sitzung geschlossen ist, wird auch der Cache der ersten Ebene beendet.

Dies ist eigentlich wünschenswert, da gleichzeitige Sitzungen getrennt voneinander mit Entitätsinstanzen arbeiten können.

Andererseits ist der Second-Level-Cache SessionFactory -Scope, dh er wird von allen Sitzungen gemeinsam verwendet, die mit derselben Session-Factory erstellt wurden. Wenn eine Entitätsinstanz anhand ihrer ID nachgeschlagen wird (entweder durch Anwendungslogik oder intern durch Hibernate, e.g. , wenn Assoziationen von anderen Entitäten zu dieser Entität geladen werden) und die Zwischenspeicherung der zweiten Ebene für diese Entität aktiviert ist, Folgendes passiert:

  • Wenn eine Instanz bereits im Cache der ersten Ebene vorhanden ist, ist dies der Fall

von dort zurückgekehrt ** Wenn keine Instanz im Cache der ersten Ebene gefunden wird und die

Der entsprechende Instanzstatus wird dann im Cache der zweiten Ebene zwischengespeichert Von dort werden die Daten abgerufen und eine Instanz zusammengestellt und zurückgegeben ** Andernfalls werden die erforderlichen Daten aus der Datenbank geladen und ein

Instanz wird zusammengestellt und zurückgegeben

Sobald die Instanz im Persistenzkontext (Cache der ersten Ebene) gespeichert ist, wird sie bei allen nachfolgenden Aufrufen innerhalb derselben Sitzung von dort zurückgegeben, bis die Sitzung geschlossen wird oder die Instanz manuell aus dem Persistenzkontext entfernt wird. Der geladene Instanzstatus wird auch im L2-Cache gespeichert, wenn er noch nicht vorhanden war.

3. Region Fabrik

Hibernate Second-Level-Caching ist so konzipiert, dass der aktuelle Cache-Provider nicht erkannt wird. Der Ruhezustand muss nur mit einer Implementierung der Schnittstelle org.hibernate.cache.spi.RegionFactory zur Verfügung gestellt werden, die alle Details der tatsächlichen Cache-Provider enthält.

Grundsätzlich fungiert es als Brücke zwischen Hibernate- und Cache-Providern.

In diesem Artikel verwenden wir Ehcache als Cache-Provider , einen ausgereiften und weit verbreiteten Cache. Sie können natürlich auch einen anderen Anbieter auswählen, sofern dafür eine RegionFactory implementiert ist.

Wir fügen dem Klassenpfad die Factory-Implementierung der Ehcache-Region mit der folgenden Maven-Abhängigkeit hinzu:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-ehcache</artifactId>
    <version>5.2.2.Final</version>
</dependency>

https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22org.hibernate%22%20AND%20a%3A%22hibernate-ehcache%22 [Nehmen Sie hier die neueste Version von hemernate-ehcache . Stellen Sie jedoch sicher, dass die hibernate-ehcache -Version der Hibernate-Version entspricht, die Sie in Ihrem Projekt verwenden, e.g. . Wenn Sie hibernate-ehcache 5.2.2.Final wie in diesem Beispiel verwenden, sollte die Version von Hibernate ebenfalls sein 5.2.2.Final .

Das hibernate-ehcache -Artefakt hängt von der Ehcache-Implementierung selbst ab, die somit auch transitiv im Klassenpfad enthalten ist.

4. Aktivieren der Zwischenspeicherung der zweiten Ebene

Mit den beiden folgenden Eigenschaften teilen wir Hibernate mit, dass die L2-Zwischenspeicherung aktiviert ist, und geben den Namen der Fabrikklasse der Region an:

hibernate.cache.use__second__level__cache=true
hibernate.cache.region.factory__class=org.hibernate.cache.ehcache.EhCacheRegionFactory

In persistence.xml würde es beispielsweise so aussehen:

<properties>
    ...
    <property name="hibernate.cache.use__second__level__cache" value="true"/>
    <property name="hibernate.cache.region.factory__class"
      value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
    ...
</properties>

Um die Zwischenspeicherung der zweiten Ebene (z. B. für Debugging-Zwecke) zu deaktivieren, setzen Sie einfach die Eigenschaft hibernate.cache.use second level cache__ auf false.

5. Eine Entität cachefähig machen

Um eine Entität für das Second-Level-Caching zu ermöglichen, kommentieren wir sie mit der Hibernate-spezifischen Annotation @org.hibernate.annotations.Cache und geben einen Link an: #cacheConcurrencyStrategy[Cache Concurrency Strategy].

Einige Entwickler halten es für eine gute Konvention, auch die Standard-Annotation @javax.persistence.Cacheable hinzuzufügen (obwohl dies für Hibernate nicht erforderlich ist), sodass die Implementierung einer Entitätsklasse folgendermaßen aussehen könnte:

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

Für jede Entitätsklasse verwendet Hibernate einen separaten Cache-Bereich, um den Status der Instanzen für diese Klasse zu speichern. Der Regionsname ist der vollständig qualifizierte Klassenname.

Foo -Instanzen werden beispielsweise in einem Cache mit dem Namen org.baeldung.persistence.model.Foo in Ehcache gespeichert.

Um zu überprüfen, ob die Zwischenspeicherung funktioniert, können wir einen Schnelltest wie folgt schreiben:

Foo foo = new Foo();
fooService.create(foo);
fooService.findOne(foo.getId());
int size = CacheManager.ALL__CACHE__MANAGERS.get(0)
  .getCache("org.baeldung.persistence.model.Foo").getSize();
assertThat(size, greaterThan(0));

Hier verwenden wir die Ehcache-API direkt, um sicherzustellen, dass org.baeldung.persistence.model.Foo cache nicht leer ist, nachdem wir eine Foo -Instanz geladen haben.

Sie können auch die Protokollierung von durch Hibernate generiertem SQL aktivieren und fooService.findOne (foo.getId ()) mehrmals im Test aufrufen, um sicherzustellen, dass die select -Anweisung zum Laden von Foo nur einmal (das erste Mal) gedruckt wird Bei nachfolgenden Aufrufen wird die Entitätsinstanz aus dem Cache abgerufen.

6. Cache-Parallelitätsstrategie

Basierend auf Anwendungsfällen können wir eine der folgenden Strategien für den gleichzeitigen Cache auswählen:

  • READ ONLY__ : Wird nur für Entitäten verwendet, die sich niemals ändern (Ausnahme ist

wird geworfen, wenn versucht wird, eine solche Entität zu aktualisieren). Es ist sehr einfach und performant. Sehr gut geeignet für statische Referenzdaten nicht ändern NONSTRICT READ WRITE ** : Der Cache wird nach einer Transaktion aktualisiert

geändert wurden die betroffenen Daten festgeschrieben. Somit starke Konsistenz ist nicht garantiert und es gibt ein kleines Zeitfenster in dem veraltete Daten kann aus dem Cache erhalten werden. Diese Art von Strategie ist für den Einsatz geeignet Fälle, die eine eventuelle Konsistenz tolerieren können READ WRITE__ ** : Diese Strategie garantiert eine starke Konsistenz, die es gibt

erreicht durch Verwendung von Soft-Sperren: Wenn eine zwischengespeicherte Entität aktualisiert wird, Die Sperre wird auch für diese Entität im Cache gespeichert, die freigegeben wird nachdem die Transaktion festgeschrieben ist. Alle gleichzeitig laufenden Transaktionen Zugriff auf gesperrte Einträge werden die entsprechenden Daten direkt abrufen aus der Datenbank TRANSACTIONAL ** : Cache-Änderungen werden in verteiltem XA vorgenommen

Transaktionen. Eine Änderung in einer zwischengespeicherten Entität wird entweder in der Datenbank und im Cache in derselben XA-Transaktion festgeschrieben oder zurückgesetzt

7. Cache-Verwaltung

Wenn keine Ablauf- und Aufhebungsrichtlinien definiert sind, kann der Cache unbegrenzt wachsen und schließlich den gesamten verfügbaren Speicher verbrauchen. In den meisten Fällen übergibt Hibernate Cache-Verwaltungsaufgaben wie diese den Cache-Providern, da sie tatsächlich für jede Cache-Implementierung spezifisch sind.

Wir könnten beispielsweise die folgende Ehcache-Konfiguration definieren, um die maximale Anzahl zwischengespeicherter Foo -Instanzen auf 1000 zu begrenzen:

<ehcache>
    <cache name="org.baeldung.persistence.model.Foo" maxElementsInMemory="1000"/>
</ehcache>

8. Sammlungscache

Sammlungen werden standardmäßig nicht zwischengespeichert, und wir müssen sie explizit als zwischengespeichert markieren. Zum Beispiel:

@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<Bar> bars;

   //getters and setters
}

9. Interne Vertretung des zwischengespeicherten Staates

Entitäten werden nicht im Second-Level-Cache als Java-Instanzen gespeichert, sondern im disassemblierten (hydrierten) Zustand:

  • ID (Primärschlüssel) wird nicht gespeichert (wird als Teil des Cache-Schlüssels gespeichert)

  • Transiente Eigenschaften werden nicht gespeichert

  • Sammlungen werden nicht gespeichert (siehe unten für weitere Details)

  • Nicht-Assoziationswerte werden in ihrer ursprünglichen Form gespeichert

  • Für ToOne -Assoziationen wird nur die ID (Fremdschlüssel) gespeichert

Hier wird der allgemeine Hibernate-Cache-Entwurf der zweiten Ebene dargestellt, in dem das Cache-Modell das zugrunde liegende relationale Modell widerspiegelt. Dies ist platzsparend und erleichtert das Synchronisieren der beiden.

9.1. Interne Repräsentation zwischengespeicherter Sammlungen

Wir haben bereits erwähnt, dass wir ausdrücklich angeben müssen, dass eine Sammlung ( OneToMany oder ManyToMany Association) zwischengespeichert werden kann, andernfalls wird sie nicht zwischengespeichert.

Hibernate speichert tatsächlich Sammlungen in separaten Cache-Bereichen, eine für jede Sammlung. Der Regionsname ist ein vollständig qualifizierter Klassenname und der Name der Auflistungseigenschaft. Beispiel:

org.baeldung.persistence.model.Foo.bars . Dies gibt uns die Flexibilität, separate Cache-Parameter für Sammlungen zu definieren, e.g. Eviction/Expiration-Richtlinie.

Es ist auch wichtig zu erwähnen, dass nur IDs von Entitäten, die in einer Auflistung enthalten sind, für jeden Auflistungseintrag zwischengespeichert werden. Dies bedeutet, dass es in den meisten Fällen sinnvoll ist, die enthaltenen Entitäten auch in den Cache zu setzen.

10. Cache-Invalidierung für HQL-DML-Abfragen und native Abfragen

Wenn es sich um HML-Anweisungen im DML-Stil ( insert -, update - und delete -HQL-Anweisungen) handelt, kann Hibernate feststellen, welche Entitäten von solchen Operationen betroffen sind:

entityManager.createQuery("update Foo set ... where ...").executeUpdate();

In diesem Fall werden alle Foo-Instanzen aus dem L2-Cache entfernt, während andere zwischengespeicherte Inhalte unverändert bleiben.

Wenn es sich um native SQL-DML-Anweisungen handelt, kann Hibernate jedoch nicht erraten, was aktualisiert wird. Daher wird der gesamte Cache der zweiten Ebene ungültig:

session.createNativeQuery("update FOO set ... where ...").executeUpdate();

Das ist wahrscheinlich nicht das, was Sie wollen! Die Lösung besteht darin, Hibernate mitzuteilen, welche Entitäten von nativen DML-Anweisungen betroffen sind, sodass nur Einträge entfernt werden können, die sich auf Foo -Entitäten beziehen:

Query nativeQuery = entityManager.createNativeQuery("update FOO set ... where ...");
nativeQuery.unwrap(org.hibernate.SQLQuery.class).addSynchronizedEntityClass(Foo.class);
nativeQuery.executeUpdate();

Wir haben auch auf die native SQLQuery -API des Ruhezustands zurückgegriffen, da diese Funktion (noch) nicht in JPA definiert ist.

Beachten Sie, dass das Vorstehende nur für DML-Anweisungen gilt ( insert , update , delete und native Funktions-/Prozeduraufrufe). Native select -Abfragen machen den Cache nicht ungültig.

11. Abfrage-Cache

Ergebnisse von HQL-Abfragen können auch zwischengespeichert werden. Dies ist nützlich, wenn Sie häufig eine Abfrage für Entitäten ausführen, die sich selten ändern.

Um den Abfrage-Cache zu aktivieren, setzen Sie den Wert der Eigenschaft hibernate.cache.use query cache auf true :

hibernate.cache.use__query__cache=true

Dann müssen Sie für jede Abfrage explizit angeben, dass die Abfrage zwischengespeichert werden kann (über einen Abfragehinweis org.hibernate.cacheable ):

entityManager.createQuery("select f from Foo f")
  .setHint("org.hibernate.cacheable", true)
  .getResultList();

11.1. Best Practices für den Abfrage-Cache

Im Folgenden finden Sie einige Richtlinien und bewährte Methoden für das Abfragen-Caching :

  • Wie bei Sammlungen werden nur IDs von Entitäten als Ergebnis zurückgegeben

einer zwischengespeicherten Abfrage werden zwischengespeichert, daher wird dringend empfohlen, den Cache der zweiten Ebene für solche Entitäten zu aktivieren.

  • Für jede Kombination von Abfrageparametern gibt es einen Cache-Eintrag

Werte (Bindungsvariablen) für jede Abfrage. Daher sind Abfragen, für die Sie viele unterschiedliche Kombinationen von Parameterwerten erwarten, keine guten Kandidaten für das Caching.

  • Abfragen, die Entitätsklassen beinhalten, für die es häufig gibt

Änderungen in der Datenbank sind ebenfalls keine guten Kandidaten für das Caching, da sie immer dann ungültig werden, wenn sich Änderungen an einer der an der Abfrage teilnehmenden Entität befinden, unabhängig davon, ob die geänderten Instanzen als Teil des Abfrageergebnisses zwischengespeichert werden.

  • Standardmäßig werden alle Abfrage-Cache-Ergebnisse in gespeichert

org.hibernate.cache.internal.StandardQueryCache region. Wie beim Entity-/Collection-Caching können Sie die Cache-Parameter für diesen Bereich anpassen, um Ausschluss- und Ablaufrichtlinien entsprechend Ihren Anforderungen zu definieren. Für jede Abfrage können Sie auch einen benutzerdefinierten Regionsnamen angeben, um unterschiedliche Einstellungen für verschiedene Abfragen bereitzustellen.

  • Für alle Tabellen, die im Rahmen von zwischengespeicherten Abfragen abgefragt werden

Der Ruhezustand speichert die Zeitstempel der letzten Aktualisierung in einem separaten Bereich namens org.hibernate.cache.spi.UpdateTimestampsCache . Die Kenntnis dieses Bereichs ist sehr wichtig, wenn Sie die Abfragezwischenspeicherung verwenden, da Hibernate diese verwendet, um zu überprüfen, ob zwischengespeicherte Abfrageergebnisse nicht veraltet sind. Die Einträge in diesem Cache dürfen nicht gelöscht/abgelaufen sein, solange in den Abfrageergebnisbereichen zwischengespeicherte Abfrageergebnisse für die entsprechenden Tabellen vorhanden sind. Deaktivieren Sie das automatische Löschen und das Ablaufen für diesen Cache-Bereich am besten, da dies ohnehin nicht viel Speicherplatz beansprucht.

12. Fazit

In diesem Artikel haben wir untersucht, wie Sie den Hibernate-Second-Level-Cache einrichten.

Wir haben gesehen, dass es ziemlich einfach zu konfigurieren und zu verwenden ist, da Hibernate all die harte Arbeit hinter den Kulissen macht und die Cache-Auslastung der zweiten Ebene für die Geschäftslogik der Anwendung transparent macht.

Die Implementierung dieses Cache-Lernprogramms für den Hibernate-Second-Level-Cache ist verfügbar https://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-jpa Dies ist ein auf Maven basierendes Projekt. Es sollte daher einfach zu importieren und so auszuführen sein, wie es ist.