- jedna tabela na klasę konkretną (Single table per Concrete Class),
- jedna tabela na hierarchię klas (Single table per Class Hierarchy),
- złączenie podklasy (Joined Subclass).
Specyfikacja JPA, z użyciem adnotacji
@Inheritance
, umożliwia zastosowanie każdej z tych trzech strategii reprezentacji dziedziczenia.
Przykład - dziedzina problemu
Dla celów niniejszego artykułu zostanie wykorzystany poniższy model danych: Jest to prosta hierarchia klas, z których abstrakcyjna klasaOsoba
jest klasą nadrzędną, a klasy OsobaPrawna
i OsobaFizyczna
z niej dziedziczą.
Jedna tabela na klasę konkretną (Single table per Concrete Class)
Wg. tej strategii model relacyjny prezentowałby się następująco: Te podejście przewiduje, iż na każdą klasę konkretną będzie przypadać jedna tabela w modelu relacyjnym. W naszym przypadku zostaną utworzone tabele dla klasOsobaPrawna
i OsobaFizyczna
. Przy czym tabele te będą zawierać również kolumny reprezentujące dziedziczone atrybuty. Tu trzeba zaznaczyć, że nie ma możliwości nadpisania mapowania dziedziczonych atrybutów - nie zadziała adnotacja @AttributeOverride(s)
.Klasa
Osoba
nie będzie jawnie odwzorowana w modelu relacyjnym. Ta cecha sprawia, że polimorficzne asocjacje (tj. ogólne odwoływanie się do bytu Osoba
, a nie do konkretnego rodzaju osoby) stają się nie możliwe, a polimorficzne zapytania bardzo trudne (w zapytaniach używa się unii, bądź preparuje się oddzielne zapytanie dla każdej podklasy).Poniżej zamieszczam wycinki kodów definicji klas z mapowaniem:
@Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public abstract class Osoba { ... }
@Entity(name="osoba_prawna") public class OsobaPrawna extends Osoba { ... }
@Entity(name="osoba_fizyczna") public class OsobaFizyczna extends Osoba { ... }Pełne źródła w postaci projektu maven'owego można pobrać tutaj.
Jedna tabela na hierarchię klas (Single table per Class Hierarchy)
Wg. tej strategii model relacyjny prezentowałby się następująco: W tym podejściu jednej hierarchii klas przypada tylko jedna tabela. W naszym przypadku oznacza to, że wszystkie klasy (Osoba
, OsobaPrawna
, OsobaFizyczna
) będą reprezentowane tylko przez jedną tabelę. Tabela ta oprócz kolumn reprezentujących atrybuty tych klas, będzie posiadać kolumnę flagę (discriminator) - os_typ
, która będzie określać jakiej klasy konkretnej jest dany rekord. Nie jest koniecznie definiowanie w klasie atrybutu reprezentującego discriminator'a, ale jak już jest to robione, to w mapowaniu tego atrybutu musi być wyłączona opcja zapisu i uaktualniania tej wartości, gdyż nie można jej zmieniać manualnie.Te rozwiązanie nie posiada wad związanych z polimorfizmem, które posiadało poprzednie podejście. Jednak posiada inną wadę związaną z możliwościami definiowania ograniczeń kolumn (przedewszystkim
not null
) specyficznych dla klas konkretnych. Np. nie można wymusić żeby kolumna mapująca OsobaPrawna.regon
nie mogła przyjmować wartości pustych, gdyż tego warunku nie sprosta żaden rekord reprezentujący obiekt OsobaFizyczna
.Poniżej zamieszczam wycinki kodów definicji klas z mapowaniem:
@Entity(name="osoba") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="os_typ") public abstract class Osoba { ... }
@Entity @DiscriminatorValue(value="OP") public class OsobaPrawna extends Osoba { ... }
@Entity @DiscriminatorValue(value="OF") public class OsobaFizyczna extends Osoba { ... }Pełne źródła w postaci projektu maven'owego można pobrać tutaj.
Złączenie podklasy (Joined Subclass)
Wg. tej strategii model relacyjny prezentowałby się następująco: W tym podejściu każda klasa (nawet abstrakcyjna) ma w relacyjnym modelu swoją reprezentacje w postaci tabeli. Oznacza to, że dane konkretnego obiektu będą zapisane po części w tabeli klasy abstrakcyjnej oraz po części w tabeli klasy konkretnej. U nas np. atrybuty obiektuOsobaPrawna
będą przechowywane w tabeli OsobaPrawna
jak oraz w zakresie dziedziczonych atrybutów w tabeli Osoba
.Byt dziedziczący musi mieć odwołanie do bytu z którego dziedziczy. W roli tego odwołania może być wspólny klucz główny. I tak wiersz w tabeli klast
OsobaPrawna
będzie miał klucz główny równy co do wartości do klucza wiersza w tabeli klasy Osoba
.Te podejście nie ma wad poprzednich rozwiązań. Ponadto taki model relacyjny charakteryzuje się wyższym stopniem normalizacji w stosunku do konkurencyjnych rozwiązań. Co też może również być wadą tej strategii, gdyż wyższy stopień normalizacji oznacza, że dane są rozproszone w większej ilości tabelach, co zwiększa skomplikowanie zapytań i w pewnym stopniu czas ich wykonania.
Poniżej zamieszczam wycinki kodów definicji klas z mapowaniem:
@Entity(name="osoba") @Inheritance(strategy = InheritanceType.JOINED) public abstract class Osoba { ... }
@Entity(name="osoba_prawna") @PrimaryKeyJoinColumn(name="op_id") public class OsobaPrawna extends Osoba { ... }
@Entity(name="osoba_fizyczna") @PrimaryKeyJoinColumn(name="of_id") public class OsobaFizyczna extends Osoba { ... }Pełne źródła w postaci projektu maven'owego można pobrać tutaj.
Dziedziczenie z klasy nie będącą encją
Specyfikacja JPA przewiduje dwa warianty zachowania w przypadku dziedziczenia z klasy nie będącej encją.Wariant pierwszy - klasa bazowa nie jest oznaczona adnotacją
@MappedSuperclass
. W tym przypadku atrybuty odziedziczone są ulotne i nie będą zapisane w bazie danych po operacji utrwalenia obiektu.Wariant drugi - klasa bazowa jest oznaczona adnotacją
@MappedSuperclass
. Wówczas dziedziczone atrybuty są trwałe wg. mapowania zdefiniowanego w tej bazowej klasie. Przy czym istnieje możliwość nadpisania tych mapowań poprzez użycie adnotacji @AttributeOverride(s)
.Moim zdaniem mapowane klasy nadrzędne są ciekawym rozwiązaniem i można go użyć chociażby do definicji klasy bazowej wszystkich encji, która będzie zawierać atrybuty zawierające informacje o dokonywanych na encji zmianach.
Poniżej zamieszczam przykład użycia mapowanej klasy:
@MappedSuperclass public class EncjaAudytowalna { private String uzytkownikModyfikujacy; private Date dataModyfikacji; @Column(name="audyt_uzytkownik") public String getUzytkownikModyfikujacy() { return uzytkownikModyfikujacy; } public void setUzytkownikModyfikujacy(String uzytkownikModyfikujacy) { this.uzytkownikModyfikujacy = uzytkownikModyfikujacy; } @Column(name="audyt_data") @Temporal(value=TemporalType.TIMESTAMP) public Date getDataModyfikacji() { return dataModyfikacji; } public void setDataModyfikacji(Date dataModyfikacji) { this.dataModyfikacji = dataModyfikacji; } }
@Entity @AttributeOverride(name="uzytkownikModyfikujacy", column=@Column(name="uz_au_um")) public class Uzytkownik extends EncjaAudytowalna { private Long id; private String name; @Id @GeneratedValue(strategy=GenerationType.AUTO) public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Column(name="nazwa") public String getName() { return name; } public void setName(String name) { this.name = name; } }
Zasoby
Hibernate Annotations - 2.2.4. Mapping inheritanceSpecyfikacja JPA
Brak komentarzy:
Prześlij komentarz