Pemetaan Warisan Hibernate

1. Gambaran keseluruhan

Pangkalan data hubungan tidak mempunyai cara mudah untuk memetakan hierarki kelas ke jadual pangkalan data.

Untuk mengatasi hal ini, spesifikasi JPA menyediakan beberapa strategi:

  • MappedSuperclass - kelas induk, tidak boleh menjadi entiti
  • Jadual Tunggal - entiti dari kelas yang berbeza dengan nenek moyang bersama ditempatkan dalam satu jadual
  • Jadual Bergabung - setiap kelas mempunyai jadualnya dan meminta entiti subkelas memerlukan bergabung dengan jadual
  • Table-Per-Class - semua sifat kelas ada di dalam jadualnya, jadi tidak perlu bergabung

Setiap strategi menghasilkan struktur pangkalan data yang berbeza.

Pewarisan entiti bermaksud bahawa kita boleh menggunakan pertanyaan polimorfik untuk mendapatkan semula semua entiti sub-kelas ketika membuat pertanyaan untuk kelas super.

Oleh kerana Hibernate adalah implementasi JPA, ini berisi semua hal di atas serta beberapa ciri khusus Hibernate yang berkaitan dengan pewarisan.

Pada bahagian seterusnya, kami akan membahas strategi yang tersedia dengan lebih terperinci.

2. MappedSuperclass

Dengan menggunakan strategi MappedSuperclass , warisan hanya dapat dilihat di kelas, tetapi bukan model entiti.

Mari mulakan dengan membuat kelas Person yang akan mewakili kelas induk:

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

Perhatikan bahawa kelas ini tidak lagi mempunyai anotasi @Entity , kerana ia tidak akan berterusan dalam pangkalan data dengan sendirinya.

Seterusnya, mari tambahkan subkelas Pekerja :

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

Dalam pangkalan data, ini akan sesuai dengan satu jadual "MyEm Employee" dengan tiga lajur untuk bidang yang diisytiharkan dan diwarisi dari sub-kelas.

Sekiranya kita menggunakan strategi ini, nenek moyang tidak boleh mengandungi kaitan dengan entiti lain.

3. Jadual Tunggal

Strategi Jadual Tunggal membuat satu jadual untuk setiap hierarki kelas. Ini juga merupakan strategi lalai yang dipilih oleh JPA jika kita tidak menentukannya secara eksplisit.

Kami dapat menentukan strategi yang ingin kami gunakan dengan menambahkan anotasi @Inheritance ke kelas super:

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

Pengecam entiti juga ditakrifkan dalam kelas super.

Kemudian, kita boleh menambahkan entiti sub-kelas:

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

3.1. Nilai Diskriminasi

Oleh kerana rekod untuk semua entiti akan berada dalam jadual yang sama, Hibernate memerlukan cara untuk membezakan antara mereka.

Secara lalai, ini dilakukan melalui ruangan diskriminator yang disebut DTYPE yang mempunyai nama entiti sebagai nilai.

Untuk menyesuaikan lajur diskriminator, kami dapat menggunakan anotasi @DiscriminatorColumn :

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

Di sini kami telah memilih untuk membezakan entiti sub-kelas MyProduct dengan lajur integer yang dipanggil product_type .

Seterusnya, kita perlu memberitahu Hibernate nilai apa yang akan dimiliki oleh setiap rekod sub-kelas untuk lajur product_type :

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

Hibernate menambahkan dua nilai lain yang telah ditentukan yang boleh diambil oleh anotasi: " null " dan " not null ":

  • @DiscriminatorValue ("null") - bermaksud bahawa sebarang baris tanpa nilai diskriminator akan dipetakan ke kelas entiti dengan anotasi ini; ini boleh digunakan untuk kelas akar hierarki
  • @DiscriminatorValue ("not null") - mana-mana baris dengan nilai diskriminator yang tidak sepadan dengan yang berkaitan dengan definisi entiti akan dipetakan ke kelas dengan anotasi ini

Daripada lajur, kita juga boleh menggunakan anotasi @DiscriminatorFormula khusus Hibernate untuk menentukan nilai pembezaan:

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

Strategi ini mempunyai kelebihan prestasi pertanyaan polimorfik kerana hanya satu jadual yang perlu diakses ketika membuat pertanyaan mengenai entiti induk. Sebaliknya, ini juga bermaksud bahawa kita tidak lagi dapat menggunakan batasan NOT NULL pada sifat entiti sub-kelas .

4. Jadual Bergabung

Dengan menggunakan strategi ini, setiap kelas dalam hierarki dipetakan ke jadualnya. Satu-satunya lajur yang berulang kali muncul di semua jadual adalah pengecam, yang akan digunakan untuk bergabung dengannya bila diperlukan.

Mari buat kelas super yang menggunakan strategi ini:

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

Kemudian, kita hanya boleh menentukan sub-kelas:

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

Perhatikan bahawa ini juga berfungsi untuk kelas atau antara muka super, sama ada @MappedSuperclass atau tidak. Perbezaan dari pertanyaan HQL biasa ialah kita harus menggunakan nama yang memenuhi syarat sepenuhnya kerana mereka bukan entiti yang diuruskan oleh Hibernate.

Sekiranya kita tidak mahu sub-kelas dikembalikan dengan jenis pertanyaan ini, maka kita hanya perlu menambahkan anotasi Hibernate @Polymorphism pada definisinya, dengan jenis EXPLICIT :

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

Dalam kes ini, apabila pertanyaan untuk Item, yang beg rekod tidak akan dikembalikan.

7. Kesimpulannya

Dalam artikel ini, kami telah menunjukkan strategi yang berbeza untuk memetakan warisan di Hibernate.

Kod sumber lengkap contoh boleh didapati di GitHub.