Entity Inheritance’ın Püf Noktaları

Gökhan Birinci
Kod Gemisi
Published in
4 min readSep 18, 2018

Herhangi bir OOP dilinde olduğu üzere, ortak alanları(propertyleri) tekrar kullanmayı engellemek yerine değişen davranışlar için inheritance kullanabiliriz. Bu yazımda Entity Inheritance’ın nasıl kullanılacağını ve JPA Specification’ın bu durumu nasıl ele aldığını anlatacağım.

Entityler hakkında …

  • Entityler class inheritance’ını, polymorphic ilişkileri ve polymorphic sorguları destekler.
  • Entity classlar Entity olmayan classları extend edebilir. Aynı şekilde Entity olmayan classlar da Entity classlarını extend edebilir.
  • Entity classları abstract ya da concrete olabilir.

İlişkisel veritabanlarının sınıf hiyerarşilerini veritabanı tablolarına eşlemek(mapping) için kolay bir yol yoktur.

Bunu ele almak için JPA Specification’ ın çeşitli yöntemleri vardır:

  1. MappedSuperclass
  2. Single Table
  3. Joined Table
  4. Table-Per-Class

Şimdi bu yöntemleri ayrıntılı olarak inceleyeceğiz. Çoğumuzun sıklıkla kullandığı bir yöntem olan MappedSuperclass ile başlayalım.

1. MappedSuperclass

Entityler, persistence durumunu ve mapping bilgilerini içeren ve Entity olmayan bir superclass’tan inherit olabilir. Bu superclass “@Entity” notasyonu ile tanımlanmaz ve Java Persistence Provider tarafından entity olarak belirlenmez. Bu classları genellikle ortak state ve mapping bilgisi olan entitylerimiz olduğunda kullanırız.

Yukarıdaki entity sonucunda veritabanında sadece User tablosu oluşacak ve fieldları id, deleted, creation_time, deletion_time, username ve password şeklinde oluşacaktır.

2. SINGLE TABLE

Explicit bir şekilde başka bir strategy belirlemediğimiz sürece default olarak belirlenen strategydir. Hiyerarşide superclassı extend eden her class için sadece bir tane veritabanı tablosu oluşur.

Bu classlar sonucunda Base_Item adında bir tablo ile item_id, barcode, price, category_name ve brand fieldları oluşacaktır.

Bu yaklaşım polymorphic sorguları yapabileceğiniz en verimli ve en performanslı yöntemdir. Ancak bu yöntemi kullanırken bazı sakıncalar vardır:

  • Bir örnekle anlatmak gerekirse; bizim örneğimizdeki Toy entitysini persist ettiğimiz zaman Toy entitysinin fieldlarını doldurup kendisine ait olmayan fieldları null olarak tanımlayacaktır. Bu sebeple NotNull gibi constraintleri kullanamazsınız.
  • Entitylerin hepsini bir veritabanı tablosuna yazdığımız zaman Hibernate’in bir şekilde bunların hangi entityle ilişkilendireceğini bilmesi gerekir. Bu bilgide default olarak entity attributeü olmayan “discriminator column”da saklanır. Bu column ya entitylerin superclass’ında “@DiscriminatorColumn” notasyonunu kullanarak tanımlanabilir ya da default olarak Hibernate DTYPE’ı kullanır.

Alt entitylerin de tanımlanması yine superclass’a benzer bir şekilde. Ancak bu sefer de “@DiscriminatorValue” notasyonunu eklememiz gerekir. Bu entity sınıfının ayırt edici değerini belirtir, böylece peristence provider ilgili her veritabanı kaydını bir concrete entity classına eşleyebilir.

Eğer Hibernate kullanıyorsanız “@DiscriminatorValue” notasyonunu kullanmak isteğe bağlıdır(optional). Eğer bu notasyonu kullanmazsanız Hibernate default ayırt edici özellik olarak entity name’i kullanır. Ama Hibernate’in default davranışı JPA Specification’ında belirtilmediği için güvenmemeliyiz.

Önceden de söylediğimiz gibi bu yöntem en verimli ve performanslı yöntemdir. Hibernate verileri fetch ederken kullandığımız bu yöntemde herhangi bir join ya da union işlemi uygulamadan sadece “@DiscriminatorValue” da tanımladığımız değere göre bir where clause set eder.

Örneğin : Select … from Base_Item where item = “toy”

3. JOINED TABLE

Bu stratejiyi kullanırken, hiyerarşideki tanımladığımız her sınıf kendi tablosuna eşlenir(mapped). Yani superclass ve subclasslar ayrı ayrı tablolara kaydedilir ve superclasslarda belirlenen ortak fieldlar subclasslara kaydedilmez. Ayrıca altsınıflarda üstsınıfların primary keyleri bulunur, bu durumda üstsınıfların primary keyleri aynı zamanda subclassların foreign keylerini temsil etmiş olur.

Bu durumda veri bütünlüğü açısından NotNull gibi constraintleri belirleyebiliyoruz fakat bir subclass’ı fetch etmek istediğimizde join yapmak zorunda kalırız ,bu da performansımızı düşürür.

Bu stratejinin de tanımlanması diğerlerinde olduğu gibi çok kolay;

Bu tanımlamaları yaptıktan sonra veritabanımızda tablolarımız şu şekilde olacak:

Base_Item : barcode ve item_id.
Glasses : brand, type, item_id
Toy : price, item_id

4. TABLE_PER_CLASS

Bu yöntem aslında MappedSuperclass yöntemiyle çok benzer. Temel fark ise artık superclass’ın da Entity olması. Bu yöntemde de hala concrete classların hepsinin kendilerine ait birer tablosu vardır. Bu eşleme(mapping) bize polymorphic sorguları ve superclass ile ilişkiler tanımlamamıza olanak sağlar. Fakat bu tablo yapısı polymorphic sorgulara çok fazla karmaşıklık getirir, dolayısıyla bunu kullanmaktan kaçınmalısınız.

Bu yöntemi uygularken diğer yöntemlerdeki gibi superclassımıza “@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)” notasyonunu ekliyoruz ve concrete classlarımızda da bu classı extend edip “@Entity” notasyonumuzu ekliyoruz.

Bu uygulamanın sonucunda veritabanında tablomuz:
User : id, deleted, creation_time, update_time, deletion_time, username, password
şeklinde oluşur.

Bu yöntemi kullanırken arka tarafta üretilen sorgu UNION ile ya da tüm subclassların ayrı bir şekilde sorgulandığı bir sorgu oluşturulduğu için bu performansı bir hayli aşağıya çekmektedir. Ayrıca önemli bir diğer konu ise artık otomatize edilmiş primary key’i kullanamıyor oluşumuz, bu durumu kısaca özetlemek gerekirse auto key generation sütun tabanlı(column based); bizim ise Table-Per-Class stratejisinde oluşturulacak her classın kendine ait bir unique ID’si bulunuyor. Tabi ki bunun çözümü için çeşitli yöntemler kullanabiliriz:

Örneğin :

Tüm bunlardan çıkarım yaparsak: Gerçekten ihtiyacınız yoksa bu yöntemleri kullanmayın. Gerçekten neye ihtiyacınız olduğunu biliyorsanız ve eminseniz, üzerinde durduğumuz bu başlıkları kısaca özetleyelim:

  • Single Table; polymorphic sorgulara ve ilişkilere ihtiyacımız varsa ve performansta bizim için önemliyse tercih etmemiz gereken strateji ancak dikkat etmemiz gereken konu veri bütünlüğü(!) Bu stratejiyi kullanırken constraintleri kullanamıyoruz.
  • Joined Table; eğer veri bütünlüğü bizim için performans, polymorphic sorgular ve ilişkilerden daha önemliyse seçmemiz gereken strateji olacaktır.
  • Table-Per-Class; Polymorphic sorgulara ve ilişkilere ihtiyacımız yoksa bizim için daha uygun olan strateji bu olacaktır. Ayrıca bize constraintler tanımlayabilmemiz sayesinde veri bütünlüğünü oluşturma olanağı sağlar. Ayrıca bu stratejide polymorphic sorgu ve ilişkiler sağlayabiliriz ancak unutmamamız gereken; bu sorgular kompleks tablo yapımızdan dolayı bizim için çok kullanışsız ve performanssızdır.

Bu yazımda temelde Entity Inheritance’ın nasıl ele alındığını, JPA Specification’da bulunan stratejilerin en önemli özelliklerini anlattım. Bu yazının devam yazısında da bu konuyla ilgili uygulamamızda Business Logic’i nasıl kurmamız gerektiğini ve ilgili design patternlarla imlementasyonunu anlatacağım.

--

--