Pemetaan Warisan Hibernasi

1. Ikhtisar

Database relasional tidak memiliki cara langsung untuk memetakan hierarki kelas ke tabel database.

Untuk mengatasi hal ini, spesifikasi JPA menyediakan beberapa strategi:

  • MappedSuperclass - kelas induk, tidak boleh berupa entitas
  • Tabel Tunggal - entitas dari kelas yang berbeda dengan leluhur yang sama ditempatkan dalam satu tabel
  • Tabel Gabungan - setiap kelas memiliki tabelnya sendiri dan meminta entitas subkelas memerlukan penggabungan tabel
  • Tabel-Per-Kelas - semua properti kelas, ada dalam tabelnya, jadi tidak diperlukan gabungan

Setiap strategi menghasilkan struktur database yang berbeda.

Warisan entitas berarti kita dapat menggunakan kueri polimorfik untuk mengambil semua entitas sub-kelas saat membuat kueri untuk kelas super.

Karena Hibernate adalah implementasi JPA, Hibernate berisi semua hal di atas serta beberapa fitur khusus Hibernasi yang terkait dengan pewarisan.

Di bagian selanjutnya, kita akan membahas strategi yang tersedia secara lebih rinci.

2. MappedSuperclass

Menggunakan strategi MappedSuperclass , pewarisan hanya terbukti di kelas, tetapi bukan model entitas.

Mari kita mulai dengan membuat kelas Person yang akan mewakili kelas induk:

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

Perhatikan bahwa kelas ini tidak lagi memiliki @ Entity penjelasan , karena tidak akan bertahan dalam database dengan sendirinya.

Selanjutnya, mari tambahkan sub-kelas Karyawan :

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

Dalam database, ini akan sesuai dengan satu tabel "MyEmployee" dengan tiga kolom untuk bidang yang dideklarasikan dan diwariskan dari sub-kelas.

Jika kita menggunakan strategi ini, leluhur tidak boleh berisi asosiasi dengan entitas lain.

3. Meja Tunggal

Strategi Tabel Tunggal membuat satu tabel untuk setiap hierarki kelas. Ini juga merupakan strategi default yang dipilih oleh JPA jika kita tidak menentukannya secara eksplisit.

Kita dapat menentukan strategi yang ingin kita gunakan dengan menambahkan anotasi @Inheritance ke super-class:

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

Pengenal entitas juga ditentukan di super-class.

Kemudian, kita dapat menambahkan entitas sub-kelas:

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

3.1. Nilai Diskriminator

Karena record untuk semua entitas akan berada di tabel yang sama, Hibernate memerlukan cara untuk membedakannya.

Secara default, ini dilakukan melalui kolom diskriminator yang disebut DTYPE yang memiliki nama entitas sebagai nilai.

Untuk menyesuaikan kolom diskriminator, kita 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 membedakan entitas sub-kelas MyProduct dengan kolom integer yang disebut product_type .

Selanjutnya, kita perlu memberi tahu Hibernate nilai apa yang akan dimiliki setiap record sub-kelas untuk kolom product_type :

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

Hibernate menambahkan dua nilai yang telah ditentukan sebelumnya yang dapat digunakan oleh anotasi: " null " dan " not null ":

  • @DiscriminatorValue (“null”) - berarti setiap baris tanpa nilai diskriminator akan dipetakan ke kelas entitas dengan penjelasan ini; ini dapat diterapkan ke kelas root dari hierarki
  • @DiscriminatorValue ("bukan null") - setiap baris dengan nilai diskriminator yang tidak cocok dengan salah satu yang terkait dengan definisi entitas akan dipetakan ke kelas dengan anotasi ini

Alih-alih kolom, kita juga dapat menggunakan anotasi @DiscriminatorFormula khusus Hibernate untuk menentukan nilai yang membedakan:

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

Strategi ini memiliki keuntungan dari kinerja kueri polimorfik karena hanya satu tabel yang perlu diakses saat membuat kueri entitas induk. Di sisi lain, ini juga berarti bahwa kita tidak dapat lagi menggunakan batasan NOT NULL pada properti entitas sub-kelas .

4. Tabel Bergabung

Dengan menggunakan strategi ini, setiap kelas dalam hierarki dipetakan ke tabelnya. Satu-satunya kolom yang berulang kali muncul di semua tabel adalah pengenal, yang akan digunakan untuk menggabungkannya 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 cukup mendefinisikan 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 bahwa ini juga berfungsi untuk semua kelas super atau antarmuka, apakah itu @MappedSuperclass atau bukan. Perbedaan dari query HQL biasa adalah kita harus menggunakan nama yang memenuhi syarat karena mereka bukan entitas yang dikelola Hibernate.

Jika kita tidak ingin sub-kelas dikembalikan oleh jenis query ini, maka kita hanya perlu menambahkan anotasi Hibernate @Polymorphism ke definisinya, dengan tipe EXPLICIT :

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

Dalam hal ini, ketika query untuk Produk, para Bag catatan tidak akan dikembalikan.

7. Kesimpulan

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

Kode sumber lengkap dari contoh-contoh tersebut dapat ditemukan di GitHub.