همانطور که در ابتدا در فرق های Relational با Objective اشاره شد در Relational رابطه ارث بری وجود ندارد و توسط ORM میتوانیم آنرا شبیه سازی کنیم و استراتژی هایی که Hibernate برای پیاده سازی inheritance استفاده میکند در ذیل آمده است :
- Mapped Super Class
- Single Table
- Joined Table
- Table Per Class
روش اول Mapped Super Class :
در این روش یک ارث بری خام داریم یعنی یک کلاس پدر داریم که با MappedSuperClass@ آنرا به عنوان پدر معرفی کردیم و یکسری Entity@ فرزند دارد کلاس پدر Entity نیست و برای آن جدولی در دیتابیس تشکیل نمیشود فقط از فیلد های این کلاس برای ارث بری استفاده میکنیم :
@MappedSuperClass public class Account { @Id private Long id; private String owner; private BigDecimal balance; private BigDecimal interestRate; }
و در کلاس فرزندان از کلاس پدر ارث بری میکنیم و انها از نوع Entity نیز هستند و به ازای هر یک از آنها یک جدول در دیتابیس تشکیل میشود که کلیه فیلد های پدر هم در جدول وجود دارند ولی به ازای هر فرزند یک جدول ساخته میشود و جدولی تحت عنوان پدر وجود ندارد در حقیقت ما ارث بری را در سطح شی گرایی شبیه سازی کردیم و در دنیای رابطه ای ارتباطی برای ارث بری وجود ندارد چون جداول فرزند F.key به جدول پدر ندارند که ساده ترین حالت ارث بری میباشد :
@Entity public class DebitAccount extends Account { private BigDecimal overraftFee; } @Entity public class CreaditAccount extends Account { private BigDecimal ceditLimit; }
روش دوم Single Table :
چنانچه ما نوع ارث بری را مشخص نکنیم در JPA این حالت پیش فرض ارث بری میباشد ، ما برای تعیین نوع ارث بری توسط Inheritance@ انرا مشخص میکنیم
در این روش یک جدول برای کلیه حالت های ارث بری داریم که کلیه فیلد های پدر و فرزندان را در یک جدول داریم که برای هر فرزند ستون های مختص به خودش پر میشود و یک ستون مجزا میگیریم که نوع ابجکت فرزند از طریق آن قابل شناسایی باشد
پس ما یک ابجکت پدر با فیلد های مشترک داریم و یک ستون تایپ که فرزندان از روی آن تعیین میشوند که به آن discriminator column میگویند که آنرا به هایبرنیت معرفی میکنیم و هایبرنیت از روی آن فرزندان را میتواند پر کند و داشتن این ستون در هایبرنیت در این روش اجباری است و اگر discriminator column را معرفی نکنیم هایبرنیت نام این ستون را DTYPE در نظر میگیرد و برای مقدارش نام Entity را ذخیره میکند پس بهتر است خودمان این ستون را ایجاد کنیم و بعدا به هایبرنیت معرفی کنیم
اگر ما نخواهیم هایبرنیت از نام کلاس های فرزند به عنوان discriminator استفاده کند میتوانیم بالای کلاس های فرزند DiscriminatorValue@ میگذاریم و بهش مقدار رشته ای یا عددی میتوانیم بدهیم
بالای کلاس super با DiscriminatorColumn@ ستون discriminator یا همان جدا کننده را باید معرفی کنیم
کلاس پدر :
@Entity(name="products") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="product_type", discriminatorType = DiscriminatorType.INTEGER) public class MyProduct { // ... }
کلاس فرزندان :
@Entity @DiscriminatorValue("1") public class Book extends MyProduct { // ... } @Entity @DiscriminatorValue("2") public class Pen extends MyProduct { // ... }
چنانچه هیچ ستون مجزایی برای discriminator نداشته باشیم میتوانیم توسط DiscriminatorFormula@ از طریق نال بود یا نال نبودن یک ستون دیگر که مختص یکی از فرزندان است مشخص کنیم که رکورد متعلق به کدام نوع کلاس فرزند است
@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorFormula("case when author is not null then 1 else 2 end") public class MyProduct { // ... }
نکته : هایبرنیت قبل از کوئری اصلی کل رکورد ها را با کلیه ستون ها select کرده و تک تک نوع آنها را از روی Discriminator Column مشخص میکند و بعد از بین مشخص شده ها کلاس فرزندان را جدا میکند و در List خروجی قرار میدهد
روش سوم Multi Table یا Joined Table :
در این حالت به ازای هر والد و فرزند یک جدول داریم که توسط F.key روابط آنها با پدرشان مشخص شده است و با join زدن ابجکت فرزندان را بدست می آورد
در این روش برعکس روش قبل دیگر نیازی به Discriminator Column نداریم
در صورتی که در جداول فرزند F.key به جدول پدر نشده باشد خودمان میتوانیم در Entity فرزند با PrimaryKeyJoinColumn@ بزاریم و خودمان فیلدی که توسط آن باید Join بزند را مشخص کنیم
** نکته : وقتی تعداد رکورد های ما در خروجی زیاد باشد در این روش چون برای دستیابی به رکورد مورد نظر در کوئری join زده میشود performance خوبی ندارد و همیشه join overhead را خواهیم داشت
@Entity @Inheritance(strategy = InheritanceType.JOINED) public class Animal { @Id private long animalId; private String species; // constructor, getters, setters }
@Entity public class Pet extends Animal { private String name; // constructor, getters, setters }
بطور پیش فرض نام F.key در جدول فرزند هم نام با P.key با جدول پدر است اگر ما با نام متفاوتی برای F.key تعیین کرده باشیم میتوانیم انرا مشخص کنیم که در کلاس پدر به کدام نام فیلد باید join زده شود :
@Entity @PrimaryKeyJoinColumn(name = "petId") public class Pet extends Animal { // ... }
روش Table Per Class :
هر کلاس جدول مختص به خود را دارد و هر جدول شامل کلیه ستون های پدر و فرزند خود میشود ، جداول F.key ندارند پس ارتباطی هم بینشان وجود ندارد و زمان واکشی از طریق union کلاس های فرزند را بدست می آورد و واقعا پرفرمانس خیلی بدی خواهیم داشت چون union بسیار کند است و مورد استفاده اش باید خیلی خاص باشد چون بسیار کند است باید هزینه overhead ان به ویژگی مورد نیازی که داریم به صرفه باشد
همچنین در این روش نمیتوانیم Id یکتا به ازای هر رکورد داشته باشیم چون جدول ها مستقل از هم هستند