JBoss: entity beans a dědičnost

22. 12. 2008
Doba čtení: 5 minut

Sdílet

Dnes se podíváme na různé způsoby ukládání objektů, které jsou si navzájem potomky a rodiči, tedy mají mezi sebou nějakou hierarchii. Ukážeme si, jaké tabulky se při daném nastavení vytvoří v databázi, jak vypadají SQL dotazy pro načtení jednoho a všech objektů a jaké s tím jsou spojené výhody a nevýhody.

JBoss AS 5.0.0.GA

Jak již bylo uvedeno ve zprávičkách, vyšel aplikační server verze 5.0.0.GA (general availability, veřejně dostupný). I když stále má své mouchy, prošel testy a plně vyhovuje JavaEE5 standardu. Rád bych vás na tomto místě vyzval, abyste i vy hlásili nalezené chyby v systému evidence chyb.

Některé problémy jsou ale také vyřešeny. Například injekce business komponent do JSP stránky, kterou jsme se snažili používat.

<%!
   @EJB InheritanceGenerator generator;
%>

Všimněte si vykřičníku v tagu <%!. Jak již víme, JSP stránka se překládá do servletu. Tento vykřičník pak říká, že následující kód bude umístěn ve výsledném servletu v části deklarace členských proměnných. Jedině tam může proběhnout injekce (nikoliv uvnitř metod při použití lokálních proměnných).

Dědičnost

Dědičnost si předvedeme na příkladu s dopravními prostředky. Mějme základní třídu Vehicle, která definuje několik vlastností.

@Entity
@Inheritance(strategy=JOINED)
abstract public class Vehicle {
   @Id @GeneratedValue(strategy = AUTO)
   private Long id;
   private String manufacturer;
   private String type;
   private String model;
   private Integer wheels;
   private Integer doors;
   ...
}

Zbytek těla třídy obsahuje jen get/set metody bez zvláštních anotací. Anotace @Inheritance určuje, jakým způsobem budou do databáze ukládáni potomci daného objektu. Možnosti jsou:

  • JOINED  – nové vlastnosti potomků jsou ukládány do zvláštních tabulek (jedna pro jednoho potomka) a pro jeho načtení je provedeno spojení s hlavní tabulkou
  • SINGLE_TABLE  – celá hierarchie objektů je uložena v jedné tabulce
  • TABLE_PER_CLASS  – jedna kompletní tabulka na každou třídu

V případě použití SINGLE_TABLE je potřeba vědět, která konkrétní třída je v záznamu uložena. To se provede vložením sloupce, kterému se říká diskriminátor. Standardně se tento sloupec jmenuje DTYPE. To je možné změnit pomocí anotace @DiscriminatorColumn. Konkrétní hodnotu uloženou do tohoto sloupce je pak možné určit u potomků pomocí anotace @DiscriminatorValue. Pokud tuto anotaci nepoužijeme, určí si server sám interní hodnotu. Situace hezky vystihuje příklad v dokumentaci.

Všimněte si, že třída Vehicle je abstraktní, tudíž nelze vytvářet objekty přímo daného typu. Přesto se nám při použití typu JOINED a SINGLE_TABLE vytvoří tabulka VEHICLE, kde se budou ukládat vlastnosti objektů. S typem JOINED to budou společné vlastnosti, s typem SINGLE_TABLE všechny vlastnosti.

V příkladu máme definovány potomky Car a Bus a navíc ještě potomka třídy Car, kterým je třída SUV.

@Entity
public class Car extends Vehicle {
  private Integer valves;
  ...
}

@Entity
public class Bus extends Vehicle {
  private Integer seats;
  ...
}

@Entity
public class SUV extends Car {
  private Boolean is4x4;
  ...
}

Dál je v příkladu použita ještě jedna bezestavová session bean InheritanceGeneratorBean, která slouží pro vygenerování objektů, abychom mohli v databázi sledovat způsob uložení dat. Za povšimnutí stojí, že do JSP stránky injektujeme tuto business komponentu do proměnné, která je typu InheritanceGenerator, což je pouze rozhraní, které session bean implementuje.

Nyní se podívejme na to, jak jsou data ve skutečnosti uložena v databázi při použití různých typů ukládání dědičnosti. Začnemě typem JOINED.

JOINED  
VEHICLE ID, DOORS, MANUFACTURER, MODEL, TYPE, WHEELS
CAR ID, VALVES
SUV ID, IS4X4
BUS ID, SEATS

Při použití hodnoty TABLE_PER_CLASS není možné mít jeden automaticky generovaný klíč pro více tabulek. Řešením by bylo mít primární klíč zvlášť pro každého potomka. V přiloženém příkladu je to řešeno vypnutím automatického generování a nastavováním primárního klíče ručně. Je samozřejmě také možné manuálně pomocí vhodných anotací (viz předchozí díly) nastavit generování primárního klíče ze sekvence.

TABLE_PER_CLASS  
VEHICLE žádná taková tabulka nebyla vytvořena
CAR ID, DOORS, MANUFACTURER, MODEL, TYPE, WHEELS, VALVES
SUV ID, DOORS, MANUFACTURER, MODEL, TYPE, WHEELS, VALVES, IS4X4
BUS ID, DOORS, MANUFACTURER, MODEL, TYPE, WHEELS, SEATS

Při použití typu SINGLE_TABLE se vytvoří jedna tabulka se sloupečkem DTYPE pro rozlišení typu uloženého objektu. Standardně se zde ukládá název třídy.

SINGLE_TABLE  
VEHICLE ID, DTYPE, DOORS, MANUFACTURER, MODEL, TYPE, WHEELS, VALVES, IS4X4, SEATS

Z výsledků je zřejmé, že nejvíce prostoru uspoříme při použití výchozí hodnoty JOINED. Mohou však nastat situace, kdy se hodí i další dva způsoby uložení. Vyzkoušejme si nyní při různém nastavení získat SQL dotazem jeden objekt, všechny objekty daného typu a všechny objekty daného typu včetně potomků.

Začněme s nastavením typu dědičnosti na JOINED a jednoduchým požadavkem – načtení všech objektů typu SUV. Na to musíme použít poměrně komplikovaný dotaz, protože třída SUV je potomkem třídy Car a třída Car zase potomkem třídy Vehicle.

SELECT * FROM vehicle RIGHT JOIN car ON vehicle.id=car.id RIGHT JOIN suv ON car.id=suv.id;

V případě, že bychom se chtěli zaměřit na jeden objekt, stačí dotaz doplnit o podmínku WHERE. Použijeme-li nastavení TABLE_PER_CLASS nebo SINGLE_TABLE, je dotaz o dost jednodušší.

SELECT * FROM suv -- pro TABLE_PER_CLASS
SELECT * FROM vehicle WHERE dtype='SUV' -- pro SINGLE_TABLE

I zde je situace s doplněním podmínky WHERE stejná. Vše se ale může změnit v případě, že bychom chtěli získat všechny objekty daného typu včetně potomků. Při dědičnosti typu JOINED musíme použít několik dotazů a výsledek si sami spojit. Sjednocení (UNION) zde není možné použít, protože dotazy vrací rozdílný počet záznamů na řádek.

SELECT * FROM vehicle RIGHT JOIN car ON vehicle.id=car.id RIGHT JOIN suv ON car.id=suv.id
SELECT * FROM vehicle RIGHT JOIN car ON vehicle.id=car.id
SELECT * FROM vehicle RIGHT JOIN bus ON vehicle.id=bus.id

Stejný přístup musíme použít i v případě nastavení TABLE_PER_CLASS, dotazy jen budou jednodušší.

SELECT * FROM suv
SELECT * FROM car
SELECT * FROM bus

Jednoduché řešení nabízí nastavení SINGLE_TABLE.

bitcoin_skoleni

SELECT * FROM vehicle;

Jak je vidět, má každé nastavení svoje výhody. Záleží jen na tom, čeho chcete dosáhnout. Jestli vám jde o maximální úsporu místa, nebo o práci s jednotlivými objekty odděleně, nebo o práci s více typy objektů zároveň…

Závěr

Na závěr mi už jen zbývá popřát vám pěkné Vánoce a štastný nový rok. Po dědičnosti přijde na řadu předposlední díl věnovaný business komponentám. Ten bude pojednávat o transakcích.

Autor článku