Vererbungszuordnung im Ruhezustand

1. Übersicht

Relationale Datenbanken haben keine einfache Möglichkeit, Klassenhierarchien auf Datenbanktabellen abzubilden.

Um dies zu beheben, bietet die JPA-Spezifikation verschiedene Strategien:

  • MappedSuperclass - Die übergeordneten Klassen können keine Entitäten sein
  • Einzelne Tabelle - Die Entitäten aus verschiedenen Klassen mit einem gemeinsamen Vorfahren werden in einer einzelnen Tabelle platziert
  • Verbundene Tabelle - Jede Klasse hat ihre Tabelle und das Abfragen einer Unterklassenentität erfordert das Verbinden der Tabellen
  • Tabelle pro Klasse - Alle Eigenschaften einer Klasse befinden sich in ihrer Tabelle, sodass kein Join erforderlich ist

Jede Strategie führt zu einer anderen Datenbankstruktur.

Entitätsvererbung bedeutet, dass wir polymorphe Abfragen verwenden können, um alle Unterklassenentitäten abzurufen, wenn wir nach einer Superklasse fragen.

Da es sich bei Hibernate um eine JPA-Implementierung handelt, enthält es alle oben genannten sowie einige Hibernate-spezifische Funktionen im Zusammenhang mit der Vererbung.

In den nächsten Abschnitten werden wir die verfügbaren Strategien detaillierter behandeln.

2. MappedSuperclass

Bei Verwendung der MappedSuperclass- Strategie ist die Vererbung nur in der Klasse erkennbar, nicht jedoch im Entitätsmodell.

Beginnen wir mit der Erstellung einer Personenklasse , die eine übergeordnete Klasse darstellt:

@MappedSuperclass public class Person { @Id private long personId; private String name; // constructor, getters, setters }

Beachten Sie, dass diese Klasse keine @ Entity- Annotation mehr hat , da sie nicht selbst in der Datenbank gespeichert wird.

Als nächstes fügen wir eine Mitarbeiter -Unterklasse hinzu:

@Entity public class MyEmployee extends Person { private String company; // constructor, getters, setters }

In der Datenbank entspricht dies einer Tabelle "MyEmployee" mit drei Spalten für die deklarierten und geerbten Felder der Unterklasse.

Wenn wir diese Strategie verwenden, können Vorfahren keine Assoziationen mit anderen Entitäten enthalten.

3. Einzelne Tabelle

Die Single Table-Strategie erstellt eine Tabelle für jede Klassenhierarchie. Dies ist auch die von JPA gewählte Standardstrategie, wenn wir keine explizit angeben.

Wir können die Strategie definieren, die wir verwenden möchten, indem wir der Superklasse die Annotation @Inheritance hinzufügen :

@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) public class MyProduct { @Id private long productId; private String name; // constructor, getters, setters }

Die Kennung der Entitäten wird auch in der Oberklasse definiert.

Dann können wir die Unterklassenentitäten hinzufügen:

@Entity public class Book extends MyProduct { private String author; }
@Entity public class Pen extends MyProduct { private String color; }

3.1. Diskriminatorwerte

Da sich die Datensätze für alle Entitäten in derselben Tabelle befinden, muss im Ruhezustand zwischen ihnen unterschieden werden können.

Standardmäßig erfolgt dies über eine Diskriminatorspalte namens DTYPE, deren Name den Namen der Entität enthält.

Um die Diskriminatorspalte anzupassen, können Sie die Annotation @DiscriminatorColumn verwenden :

@Entity(name="products") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="product_type", discriminatorType = DiscriminatorType.INTEGER) public class MyProduct { // ... }

Hier haben wir uns entschieden, MyProduct -Unterklassenentitäten durch eine Ganzzahlspalte mit dem Namen product_type zu unterscheiden .

Als Nächstes müssen wir Hibernate mitteilen, welchen Wert jeder Unterklassendatensatz für die Spalte product_type haben wird:

@Entity @DiscriminatorValue("1") public class Book extends MyProduct { // ... }
@Entity @DiscriminatorValue("2") public class Pen extends MyProduct { // ... }

Im Ruhezustand werden zwei weitere vordefinierte Werte hinzugefügt, die die Anmerkung annehmen kann: " null " und " nicht null ":

  • @DiscriminatorValue(“null”) – means that any row without a discriminator value will be mapped to the entity class with this annotation; this can be applied to the root class of the hierarchy
  • @DiscriminatorValue(“not null”) – any row with a discriminator value not matching any of the ones associated with entity definitions will be mapped to the class with this annotation

Instead of a column, we can also use the Hibernate-specific @DiscriminatorFormula annotation to determine the differentiating values:

@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorFormula("case when author is not null then 1 else 2 end") public class MyProduct { ... }

This strategy has the advantage of polymorphic query performance since only one table needs to be accessed when querying parent entities. On the other hand, this also means that we can no longer use NOT NULL constraints on sub-class entity properties.

4. Joined Table

Using this strategy, each class in the hierarchy is mapped to its table. The only column which repeatedly appears in all the tables is the identifier, which will be used for joining them when needed.

Let's create a super-class that uses this strategy:

@Entity @Inheritance(strategy = InheritanceType.JOINED) public class Animal { @Id private long animalId; private String species; // constructor, getters, setters }

Then, we can simply define a sub-class:

@Entity public class Pet extends Animal { private String name; // constructor, getters, setters }

Both tables will have an animalId identifier column. The primary key of the Pet entity also has a foreign key constraint to the primary key of its parent entity. To customize this column, we can add the @PrimaryKeyJoinColumn annotation:

@Entity @PrimaryKeyJoinColumn(name = "petId") public class Pet extends Animal { // ... }

The disadvantage of this inheritance mapping method is that retrieving entities requires joins between tables, which can result in lower performance for large numbers of records.

The number of joins is higher when querying the parent class as it will join with every single related child – so performance is more likely to be affected the higher up the hierarchy we want to retrieve records.

5. Table per Class

The Table Per Class strategy maps each entity to its table which contains all the properties of the entity, including the ones inherited.

The resulting schema is similar to the one using @MappedSuperclass, but unlike it, table per class will indeed define entities for parent classes, allowing associations and polymorphic queries as a result.

To use this strategy, we only need to add the @Inheritance annotation to the base class:

@Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public class Vehicle { @Id private long vehicleId; private String manufacturer; // standard constructor, getters, setters }

Then, we can create the sub-classes in the standard way.

This is not very different from merely mapping each entity without inheritance. The distinction is apparent when querying the base class, which will return all the sub-class records as well by using a UNION statement in the background.

The use of UNION can also lead to inferior performance when choosing this strategy. Another issue is that we can no longer use identity key generation.

6. Polymorphic Queries

As mentioned, querying a base class will retrieve all the sub-class entities as well.

Let's see this behavior in action with a JUnit test:

@Test public void givenSubclasses_whenQuerySuperclass_thenOk() { Book book = new Book(1, "1984", "George Orwell"); session.save(book); Pen pen = new Pen(2, "my pen", "blue"); session.save(pen); assertThat(session.createQuery("from MyProduct") .getResultList()).hasSize(2); }

In this example, we've created two Book and Pen objects, then queried their super-class MyProduct to verify that we'll retrieve two objects.

Hibernate can also query interfaces or base classes which are not entities but are extended or implemented by entity classes. Let's see a JUnit test using our @MappedSuperclass example:

@Test public void givenSubclasses_whenQueryMappedSuperclass_thenOk() { MyEmployee emp = new MyEmployee(1, "john", "baeldung"); session.save(emp); assertThat(session.createQuery( "from com.baeldung.hibernate.pojo.inheritance.Person") .getResultList()) .hasSize(1); }

Beachten Sie, dass dies auch für jede Superklasse oder Schnittstelle funktioniert, unabhängig davon, ob es sich um eine @MappedSuperclass handelt oder nicht. Der Unterschied zu einer normalen HQL-Abfrage besteht darin, dass wir den vollständig qualifizierten Namen verwenden müssen, da es sich nicht um von Hibernate verwaltete Entitäten handelt.

Wenn wir nicht möchten, dass eine Unterklasse von diesem Abfragetyp zurückgegeben wird, müssen wir nur die Annotation Hibernate @Polymorphism zu ihrer Definition mit dem Typ EXPLICIT hinzufügen :

@Entity @Polymorphism(type = PolymorphismType.EXPLICIT) public class Bag implements Item { ...}

In diesem Fall werden bei der Abfrage von Artikeln die Bag- Datensätze nicht zurückgegeben.

7. Fazit

In diesem Artikel haben wir die verschiedenen Strategien für die Zuordnung der Vererbung im Ruhezustand gezeigt.

Den vollständigen Quellcode der Beispiele finden Sie auf GitHub.