Persistenz in Java: Neues seit Hibernate ORM 6
Seite 2: Verbesserte Mandantenfähigkeit
Die Möglichkeit, mit einem gemeinsamen Backend mehrere voneinander getrennte Mandanten zu bedienen, ist für viele Anwendungen relevant. In Version 5 war Hibernate ORM nur dazu in der Lage, wenn die Daten der Mandanten in voneinander getrennten Datenbanken oder Datenbankschemata vorlagen. Seit Version 6 ist der als "partitioned data" bezeichnete Einsatz einer zusätzlichen Tabellenspalte zum Zuordnen der Mandanten möglich.
Hierzu muss man die Entitäten um eine zusätzliche, mit @TenantId
annotierte Eigenschaft erweitern.
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String title;
@TenantId
private String tenant;
...
}
Um die Mandanten-ID zu erhalten, erwartet Hibernate wie bereits in Version 5 eine Implementierung von CurrentTenantIdentifierResolver
. Gängige Implementierungen dieses Interfaces fragen die Mandanten-ID von der Authentifizierungskomponente ab und stellen sie Hibernate zur Verfügung. In der Vergangenheit erlaubte Hibernate ausschließlich Strings als Mandanten-IDs, aber seit Version 6.4 auch andere Eigenschaftstypen. Üblicherweise verwenden Anwendungen die Typen String, Integer oder Long.
Hibernate setzt die ID automatisch beim Persistieren einer neuen Entität und erweitert die ausgeführten Abfragen, um die Ergebnisse auf den aktuell aktiven Mandaten einzuschränken.
em.createQuery("SELECT b FROM Book b " +
"WHERE b.title = :title", Book.class)
.setParameter("title", "My Book")
.getResultList();
09:00:15,976 DEBUG [org.hibernate.SQL] -
select
b1_0.id,
b1_0.tenant,
b1_0.title,
b1_0.version
from
Book b1_0
where
b1_0.tenant = ?
and b1_0.title=?
Soft Deletes
Eine häufige Anforderung von Enterprise-Anwendungen sind Soft Deletes, um die Datensätze nicht zu löschen, sondern zu deaktivieren. Die Daten sind auf Anwenderseite nicht mehr sichtbar, stehen aber für weitere Auswertungen oder zur späteren Nachvollziehbarkeit weiterhin zur Verfügung.
In der Vergangenheit musste man dazu manuell einen Filter einsetzen und die Löschoperation überschreiben.
Seit der Version 6.4 bietet Hibernate die @SoftDelete
-Annotation.
@Entity
@SoftDelete
public class Book { ... }
Für eine damit annotierte Entität speichert Hibernate den aktuellen Status des Datensatzes in einer zusätzlichen Datenbankspalte mit dem Typ Boolean. Die optionale Eigenschaft columnName
der @SoftDelete
-Annotation legt den Spaltennamen fest. Fehlt der Name, verwendet Hibernate die Spalte deleted
.
Um inaktive Datensätze automatisch auszublenden, erweitert Hibernate alle Datenbankabfragen um eine zusätzliche Prüfung der Spalte deleted
.
Book book = em.createQuery("SELECT b FROM Book b " +
"WHERE b.title = :title",
Book.class)
.setParameter("title", "My Book")
.getSingleResult();
09:15:13,799 DEBUG [org.hibernate.SQL] -
select
b1_0.id,
b1_0.title,
b1_0.version
from
Book b1_0
where
b1_0.title=?
and b1_0.deleted=false
Solange der Datensatz aktiv ist, enthält deleted
den Wert false
. Beim Löschen der Entität führt Hibernate die Operation SQL UPDATE
statt SQL DELETE
aus und setzt den Wert von deleted
auf true
.
09:15:13,804 DEBUG [org.hibernate.SQL] -
update
Book
set
deleted=true
where
id=?
and deleted=false
and version=?
Die Annotation @SoftDelete
bietet zusätzliche Konfigurationsmöglichkeiten, um Hibernates Implementierung an Tabellenschemata anzupassen.
Intern basiert die Soft-Delete-Implementierung auf einem Boolean, der den aktuellen Status des jeweiligen Datensatzes abbildet. Wie Hibernate diesen Boolean auswertet, legt der Enum-Wert SoftDeleteType
fest. Er ist Teil der Eigenschaft strategy
von @SoftDelete
und kann ACTIVE
oder DELETED
sein.
Im Standardfall verwendet Hibernate SoftDeleteType.DELETED
. Dabei signalisiert true
, dass der Datensatz deaktiviert ist. Bei der Strategie SoftDeleteType.ACTIVE
bedeutet true
hingegen, dass der Datensatz aktiv ist.
Wer den Status des Datensatzes nicht als Boolean in der Datenbank speichern möchte, kann das Vorgehen durch die Referenz eines AttributeConverter
anpassen. Dabei handelt es sich um ein einfaches Interface mit zwei Methoden, die die Umwandlung zwischen dem Typ der Entitätseigenschaft und der Datenbankspalte implementieren.
@Entity
@SoftDelete(strategy = SoftDeleteType.ACTIVE,
columnName = "status",
converter = SoftDeleteConverter.class)
public class Book { ... }
Java Flight Recorder Events
Seit Version 6.4 kann Hibernate ORM unterschiedliche interne Ereignisse als JFR-Events (Java Flight Recorder) ausgeben. Dazu gehören unter anderem das Ausführen von JDBC Statements, das Öffnen und Schließen einer Session, das Ausführen von Flush-Operationen und die Interaktionen mit dem 2nd Level Cache.
Um das Erzeugen von JFR-Events zu aktivieren, muss man die Komponente hibernate-jfr als Abhängigkeit zum Projekt hinzufügen und die Anwendung mit dem Flag --XX:StartFlightRecording:filename=<Dateiname>.jfr
starten.
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-jfr</artifactId>
<version>${hibernate.version}</version>
</dependency>
Hibernate erzeugt daraufhin während der Ausführung der Anwendung eine Java-Flight-Recorder-Datei, die sich beispielsweise mit JDK Mission Control einlesen und auswerten lässt.