Overervingstoewijzing in slaapstand

1. Overzicht

Relationele databases hebben geen eenvoudige manier om klassehiërarchieën toe te wijzen aan databasetabellen.

Om dit aan te pakken, biedt de JPA-specificatie verschillende strategieën:

  • Toegewezen Superclass - de bovenliggende klassen kunnen geen entiteiten zijn
  • Enkele tabel - de entiteiten uit verschillende klassen met een gemeenschappelijke voorouder worden in een enkele tabel geplaatst
  • Samengevoegde tabel - elke klasse heeft zijn eigen tabel en het opvragen van een subklasse-entiteit vereist het samenvoegen van de tabellen
  • Table-Per-Class - alle eigenschappen van een klasse staan ​​in de tabel, dus geen join is vereist

Elke strategie resulteert in een andere databasestructuur.

Entiteitsovererving betekent dat we polymorfe query's kunnen gebruiken om alle subklasse-entiteiten op te halen bij het zoeken naar een superklasse.

Omdat Hibernate een JPA-implementatie is, bevat deze al het bovenstaande, evenals enkele Hibernate-specifieke functies met betrekking tot overerving.

In de volgende secties zullen we de beschikbare strategieën in meer detail bespreken.

2. Toegewezen Superclass

De ... gebruiken Toegewezen Superclass strategie is overerving alleen zichtbaar in de klasse, maar niet in het entiteitsmodel.

Laten we beginnen met het maken van een Persoon klasse die een bovenliggende klasse zal vertegenwoordigen:

@MappedSuperclass openbare klasse Persoon {@Id privé lang personId; private String naam; // constructor, getters, setters}

Merk op dat deze klasse niet langer een @Entiteit annotatie, omdat het zelf niet in de database wordt bewaard.

Laten we vervolgens een Werknemer subklasse:

@Entity openbare klasse MyEmployee breidt Persoon {privé String-bedrijf uit; // constructor, getters, setters}

In de database komt dit overeen met een "MyEmployee" tabel met drie kolommen voor de gedeclareerde en overgenomen velden van de subklasse.

Als we deze strategie gebruiken, kunnen voorouders geen associaties met andere entiteiten bevatten.

3. Enkele tafel

De Single Table-strategie creëert één tabel voor elke klassehiërarchie. Dit is ook de standaardstrategie die door JPA wordt gekozen als we er niet expliciet een specificeren.

We kunnen de strategie definiëren die we willen gebruiken door de @Erfenis annotatie bij de superklasse:

@Entity @Inheritance (strategy = InheritanceType.SINGLE_TABLE) openbare klasse MyProduct {@Id privé lang productId; private String naam; // constructor, getters, setters}

De identifier van de entiteiten wordt ook gedefinieerd in de superklasse.

Vervolgens kunnen we de subklasse-entiteiten toevoegen:

@Entity public class Book breidt MyProduct uit {private String-auteur; }
@Entity public class Pen breidt MyProduct {private String color uit; }

3.1. Discriminator-waarden

Aangezien de records voor alle entiteiten in dezelfde tabel zullen staan, Hibernate heeft een manier nodig om onderscheid te maken tussen hen.

Standaard wordt dit gedaan via een discriminatorkolom die wordt genoemd DTYPE die de naam van de entiteit als waarde heeft.

Om de discriminatorkolom aan te passen, kunnen we de @DiscriminatorColumn annotatie:

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

Hier hebben we ervoor gekozen om te differentiëren Mijn product subklasse entiteiten door een geheel getal kolom genoemd product type.

Vervolgens moeten we Hibernate vertellen welke waarde elk subklasse-record zal hebben voor de product type kolom:

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

Hibernate voegt twee andere vooraf gedefinieerde waarden toe die de annotatie kan aannemen: "nul"En"niet nulβ€œ:

  • @DiscriminatorValue ("null") - betekent dat elke rij zonder een discriminatorwaarde met deze annotatie wordt toegewezen aan de entiteitsklasse; dit kan worden toegepast op de hoofdklasse van de hiërarchie
  • @DiscriminatorValue ("niet null") - elke rij met een discriminatorwaarde die niet overeenkomt met een van de rijen die zijn gekoppeld aan entiteitsdefinities, wordt met deze annotatie aan de klasse toegewezen

In plaats van een kolom kunnen we ook de Hibernate-specifieke gebruiken @DiscriminatorFormula annotatie om de onderscheidende waarden te bepalen:

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

Deze strategie heeft het voordeel van polymorfe queryprestaties, aangezien er slechts één tabel hoeft te worden benaderd bij het opvragen van bovenliggende entiteiten. Aan de andere kant betekent dit ook dat we kunnen niet meer gebruiken NIET NUL beperkingen op subklasse entiteitseigenschappen.

4. Samengevoegde tafel

Met deze strategie wordt elke klasse in de hiërarchie toegewezen aan zijn tabel. De enige kolom die herhaaldelijk in alle tabellen voorkomt, is de identifier, die zal worden gebruikt om ze samen te voegen wanneer dat nodig is.

Laten we een superklasse maken die deze strategie gebruikt:

@Entity @Inheritance (strategy = InheritanceType.JOINED) openbare klasse Dier {@Id privé lang dierId; particuliere String-soorten; // constructor, getters, setters}

Vervolgens kunnen we eenvoudig een subklasse definiëren:

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

Beide tafels hebben een dier-id ID-kolom. De primaire sleutel van de Pet entiteit heeft ook een beperking met betrekking tot externe sleutels voor de primaire sleutel van haar moederentiteit. Om deze kolom aan te passen, kunnen we de @BuienRadarNL annotatie:

@Entity @PrimaryKeyJoinColumn (name = "petId") openbare klasse Pet breidt Animal {// ...}

Het nadeel van deze methode van overervingstoewijzing is dat voor het ophalen van entiteiten joins tussen tabellen nodig zijn, wat kan resulteren in lagere prestaties voor grote aantallen records.

Het aantal joins is hoger bij het opvragen van de bovenliggende klasse omdat deze wordt samengevoegd met elk afzonderlijk gerelateerd kind - dus de kans is groter dat de prestaties worden beïnvloed naarmate we hoger in de hiërarchie records willen ophalen.

5. Tafel per klas

De Table Per Class-strategie wijst elke entiteit toe aan zijn tabel die alle eigenschappen van de entiteit bevat, inclusief de geërfde.

Het resulterende schema is vergelijkbaar met het schema met @MappedSuperclass, maar in tegenstelling tot dit, zal tabel per klasse inderdaad entiteiten voor bovenliggende klassen definiëren, waardoor associaties en polymorfe queries als resultaat mogelijk zijn.

Om deze strategie te gebruiken, hoeven we alleen de @Erfenis annotatie bij de basisklasse:

@Entity @Inheritance (strategy = InheritanceType.TABLE_PER_CLASS) openbare klasse Voertuig {@Id privé lang voertuig-ID; particuliere String-fabrikant; // standard constructor, getters, setters}

Vervolgens kunnen we de subklassen op de standaardmanier maken.

Dit verschilt niet veel van het louter in kaart brengen van elke entiteit zonder overerving. Het onderscheid is duidelijk bij het opvragen van de basisklasse, die ook alle subklasse-records retourneert door een UNIE verklaring op de achtergrond.

Het gebruik van UNIE kan ook leiden tot inferieure prestaties bij het kiezen van deze strategie. Een ander probleem is dat we het genereren van identiteitssleutels niet langer kunnen gebruiken.

6. Polymorfe zoekopdrachten

Zoals gezegd, zal het opvragen van een basisklasse ook alle subklasse-entiteiten ophalen.

Laten we dit gedrag in actie zien met een JUnit-test:

@Test openbare leegte gegevenSubclasses_whenQuerySuperclass_thenOk () {Boek boek = nieuw boek (1, "1984", "George Orwell"); session.save (boek); Pen pen = nieuwe pen (2, "mijn pen", "blauw"); session.save (pen); assertThat (session.createQuery ("van MyProduct") .getResultList ()). hasSize (2); }

In dit voorbeeld hebben we er twee gemaakt Boek en Pen objecten en vroegen vervolgens hun superklasse Mijn product om te verifiëren dat we twee objecten ophalen.

Hibernate kan ook interfaces of basisklassen opvragen die geen entiteiten zijn, maar worden uitgebreid of geïmplementeerd door entiteitsklassen. Laten we een JUnit-test bekijken met onze @MapdeSuperclass voorbeeld:

@Test openbare ongeldig gegevenSubclasses_whenQueryMappedSuperclass_thenOk () {MyEmployee emp = nieuwe MyEmployee (1, "john", "baeldung"); session.save (emp); assertThat (session.createQuery ("van com.baeldung.hibernate.pojo.inheritance.Person") .getResultList ()) .hasSize (1); }

Merk op dat dit ook werkt voor elke superklasse of interface, of het nu een @Map_Superclass of niet. Het verschil met een gebruikelijke HQL-query is dat we de volledig gekwalificeerde naam moeten gebruiken, aangezien het geen door Hibernate beheerde entiteiten zijn.

Als we niet willen dat een subklasse wordt geretourneerd door dit type query, hoeven we alleen de Hibernate toe te voegen @Polymorphism annotatie bij de definitie, met type EXPLICIET:

@Entity @Polymorphism (type = PolymorphismType.EXPLICIT) openbare klasse Bag implementeert item {...}

In dit geval, wanneer u naar Artikelen, de Zak records worden niet geretourneerd.

7. Conclusie

In dit artikel hebben we de verschillende strategieën laten zien om overerving in Hibernate in kaart te brengen.

De volledige broncode van de voorbeelden is te vinden op GitHub.