čtvrtek 26. července 2007

Databázové dědictví a ORM nástroje

Je relativně jednoduché použít ORM nástroje jako Hibernate či jiné implementace JPA tam kde máme databázové schéma plně v rukou. Pravá síla ORM nástroje se ukáže ve chvíli, kdy máme stávající databázové schéma, které nemůžeme např. z důvodů zpětné kompatibility prakticky měnit. Právě před takovým úkolem stojím a chtěl bych se touto cestou podělit o některé postřehy.

Bohatý objektový model

Ve většině případů je objektový model, který se snažíme namapovat mnohem bohatší než ten databázový. To znamená, že jedna třída obsahuje další vložené třídy mapované v rámci stejné či napojené tabulky . V takovém případě mapujeme takzvané komponenty v Hibernate terminologii. Komponenta umožňuje řešit například následující případ.

Máme tabulku Osoby a Domy, každá z těchto tabulek obsahuje sloupce mesto, ulice a psc. Vytvoříme tedy objekt Adresa, který obsahuje tyto tři hodnoty. Adresa může být nezávisle namapována jako komponenta v rámci Osoby i Domy.

Komponentou můžeme vyřešit i případ, kdy například Adresa má svojí tabulku, ale my nechceme třídu Adresa mapovat jako entitu. Pak v mapování Osoby a Domy uděláme join tabulky pro adresu a samotnou Adresu namapujeme opět jako komponentu.

Vlastní UserType konvertor

Ve stávajícím databázovém schématu jistě najdete sloupce jejichž datový typ nezvládne ORM nástroj zkonvertovat na patřičný java typ. Například máte VARCHAR sloupec nabývající hodnot 0 a 1, který by bylo potřeba mapovat jako javovský boolean a nebo máte datum uložené jako číslo, ale v jave má vaše POJO accessor pro java.util.Date.

Přesně pro tyto konverze je určený custom value type. Stačí naimplementovat rozhraní org.hibernate.UserType a v mapování jej deklarovat a uvést u patřičné property. Ukážeme si to na příkladu konverze řetezců 0 a 1 na boolean.

  
public class ZeroOneType extends org.hibernate.type.CharBooleanType{

    protected String getFalseString() {        
        return "0";
    }

    
    protected String getTrueString() {        
        return "1";
    }

    public String getName() {
       return "0_1";
    }
}  
 
<hibernate-mapping>
 <typedef name="0_1" class="foo.ZeroOneType"></typedef>
 <class...
    <property name="deleted" type="0_1"/>
  
  

V případě tohoto konvertoru nemusíme ani přímo implementovat org.hibernate.UserType protože Gavin King a jeho partička na nás myslela a předpřipravila abstraktní třídu pro org.hibernate.type.CharBooleanType pro mapování boolean to String a naopak.

Hibernate, anotace vs. XML a další

Mohl bych pokračovat dalšími skvělymi pomůckami jako jsou mapování jedné třídy vícekrát,strategy objekty pro nastavování javovských property či tuplizery, ale to všechno jsou nadstavby, které nabízí pouze a jenom Hibernate. Jestliže bych před dvěma, dvěma a půl roky velice zvažoval použití ORM pro stávající databázová schémata dnes bych se toho díky možnostem Hibernate vůbec nebál.

Tyto nadstavby se mohou hodit i v případě, že máte databázové schéma plně pod kontrolou a můžete jej libovolně měnit. A právě tyto nadstavby zásadně odlišují klasické JPA a Hibernate. To je pro mě o další důvod víc proč se nespoléhat na JPA, ale mít vše pevně pod kontrolou díky možnostem, které nabízí Hibernate.

Anotace vs. XML

Pokud použijete nějaký ORM nástroj, tak možná narazíte na otázku jestli použít anotace a nebo XML. Hodně vývojářů zadupalo XML deskriptory po příchodu anotací hluboko do země, ale to není správný přístup. Pro mapování legacy dat mi vyšlo jako výhodnější využití právě XML. Anotace mají smysl tam kde zjednoduší vyjádření metadat oproti XML.

Anotace vypadají velice přitažlivě pro v uvozovkách jednodušší případy mapování. Pokud jste ale někdy viděli třídu, která byla anotována trochu složitěji například věci týkající se databázových typů pak byla čitelnost anotací ta tam. K tomu musí člověk připočíst, že nejenom mapovací anotace třída obsahuje a už z toho máme anotační peklo.

S těmi mapovacími anotacemi mám ještě jeden čistě architektonický problém. Moc se mi nelíbí fakt, že třída díky nim o sobě prozrazuje věci, od kterých by měl být klient odstíněn. Například se jedná o to jakým způsobem je uložena v databázi a že přímo závisí například na JPA API. Byte code závislost rovná se integrační problém pokud někomu poskytujete třídy vašeho domain modelu.

Závěr

Mapování lagacy dat se zdá díky možnostem Hibernate jako řešitelný problém. Jsem sice pořád někde na začátku cesty při řešení daného problému, který jde ještě za hodně rámec legacy dat, ale zdá se že Hibernate je dostatečné flexibilní pro jeho řešení a to stále s obrovskou přidanou hodnotou nad klasickým JDBC.