Dosud jsme při výkladu JDO zůstávali převážně na objektové úrovni a zcela výjimečně jsme se dotkli toho, jak se persistentní objekty ve skutečnosti ukládají do relační databáze. I když při běžném používání lze tuto práci ponechat na implementaci JDO, z hlediska případných optimalizací a také pro případ, že bychom potřebovali přímo přistupovat k tabulkám databáze, je vhodné přinejmenším vědět, jak jsou naše data v databázi organizována.

Reprezentace tříd v objektové databázi

V zásadě platí, že každé persistentní třídě odpovídá jedna tabulka relační databáze. Každá relační tabulka má vždy nejméně jeden sloupec, který obsahuje jednoznačný identifikátor objektu. V případě aplikační identity se složeným primárním klíčem (popsáno v předchozích článcích) může být těchto sloupců více.

Každé persistentní vlastnosti třídy odpovídá jeden sloupec tabulky. Přímo do tabulky se ukládají hodnoty primitivních datových typů, jejich příslušných objektových variant a třídy Date. Ostatní datové typy vyžadují speciální zacházení.

Název každé tabulky se odvozuje z názvu třídy (například JPOX používá implicitně název třídy, který může být v závislosti na konfiguraci převedený na velká nebo malá písmena a podobně). V některých případech je vhodné název tabulky zcela změnit, například pokud název třídy koliduje s některým klíčovým slovem SQL a mohlo by dojít k problémům při manipulaci s tabulkou v relační databázi. Proto jednotlivé implementace umožňují zadat jiný název tabulky. Protože standard JDO 1.0.1 tuto problematiku opět neřeší, používají jednotlivé implementace svoje vlastní rozšíření. Například JPOX používá rozšíření table-name:

<package name=“demo“>
    <class name=“osoba“ identity-type=“datastore“>
        <extension vendor-name=“jpox“
                   key=“table-name“ value=“PERSON“/>
        <field name=“jmeno“>
            <extension vendor-name=“jpox“ key=“length“ value=“max 50″/>
        </field>
        …
    </class>
</package>

Stejná pravidla platí pro názvy sloupců tabulek, které se obdobně odvozují z názvů jednotlivých vlastností tříd. V případě změny je u JPOX možno použít rozšíření column-name:

<package name=“demo“>
    <class name=“osoba“ identity-type=“datastore“>
        <extension vendor-name=“jpox“
                   key=“table-name“ value=“PERSON“/>
        <field name=“jmeno“>
            <extension vendor-name=“jpox“ key=“column-name“ value=“NAME“/>
            <extension vendor-name=“jpox“ key=“length“ value=“max 50″/>
        </field>
        …
    </class>
</package>

U jednotlivých implementací se názvy těchto rozšíření mohou lišit a je opět třeba hledat v dokumentaci. Standard JDO 2.0 by měl tyto vlastnosti sjednotit.

Vnořené objekty

Vnořené objekty (second class objects, SCO) nejsou ukládány ve zvláštní tabulce, ale v rámci tabulky třídy, ve které jsou vnořené. Že je objekt vnořený, je uvedeno v metadatech pomocí atributu embedded elementu <field>. Například vložený objekt třídy Adresa se v metadatech specifikuje následujícím způsobem:

<package name=“demo“>
    <class name=“osoba“ identity-type=“datastore“>
        <field name=“adresa“ embedded=“true“/>
        …
    </class>
</package>

Primitivní datové typy a odpovídající třídy jsou vnořené implicitně a ukládají se do sloupce s odpovídajícím datovým typem. U ostatních datových typů se využívá toho, že každý persistentní objekt je serializovatelný – může být převeden na pole bajtů a z něj může být opět rekonstruován. Ukládané vložené objekty se proto převedou na serializovaný tvar a uloží do sloupce typu BLOB (Binary Large Object) nebo podobného podle možností příslušného databázového systému.

Pokud objekt není vnořený, ukládá se do tabulky příslušné k jeho třídě a se svým vlastnickým objektem se propojuje prostřednictvím relací.

Relace

Jak jsme již zmínili v předchozích článcích, relace lze modelovat prostřednictvím různých mapování.

U relace 1:1 je možné buď jednostranné nebo oboustranné mapování. U jednostranného mapování objektů tříd A a B obsahuje třída A jako atribut objekt třídy B. Pokud se nejedná o vnořený objekt, projeví se to v relační tabulce třídy A tak, že bude obsahovat sloupec nebo sloupce s cizím klíčem do tabulky třídy B. V případě oboustranného mapování bude navíc tabulka třídy B obsahovat sloupec s cizím klíčem do tabulky třídy A.

U relací 1:N se používá buď normální nebo oboustranné inverzní mapování. V prvním případě objekt třídy A obsahuje kolekci objektů třídy B. K tabulkám tříd A a B proto přibude třetí tabulka, reprezentující kolekci, která bude obsahovat sloupce s cizími klíči do tabulek A a B a bude tedy vzájemně přiřazovat objekty třídy A a objekty třídy B. U oboustranného inverzního mapování je tato tabulka nahrazena dalším sloupcem v tabulce třídy B, který má význam cizího klíče do tabulky třídy A.

Relace M:N používají na každé straně normálního mapování, každá ze tříd A a B obsahuje kolekci objektů opačné třídy. Kromě tabulek pro obě třídy tedy dostaneme ještě dvě tabulky pro kolekce.

Reprezentace kolekcí

Reprezentace kolekcí v podstatě představuje stejný problém jako reprezentace relace 1:N. V reprezentaci jednotlivých základních kolekcí Set, List a Map jsou však drobné rozdíly. Předpokládejme, že kolekce je obsažena jako atribut ve třídě vlastníka A a obsahuje prvky třídy B. Jak jsme již zmínili, třída prvků obsažených v kolekci musí být specifikována v metadatech třídy A, aby mohly být prvky kolekce správně uloženy.

U všech typů kolekcí jsou možné dva druhy mapování na tabulky. Takzvané normální mapování odpovídá normálnímu mapování relací 1:N, inverzní mapování odpovídá oboustrannému inverznímu mapování. V obou případech se vytváří po jedné relační tabulce pro třídy A a B. U normálního mapování se navíc vytváří propojovací tabulka přiřazující objekty třídy B objektům třídy A. U inverzního mapování je propojovací tabulka nahrazena sloupcem s cizím klíčem v tabulce B.

U množiny (Set) při normálním mapování obsahuje spojovací tabulka sloupce s primárními klíči tabulek A a B, které se eventuálně mohou skládat z více sloupců. Všechny sloupce spojovací tabulky tvoří její primární klíč, což vylučuje duplicitu řádků a zajišťuje, že množina nebude obsahovat duplicitní prvky, jak vyplývá z její definice.

Normální mapování u seznamu (List) je řešeno obdobným způsobem, spojovací tabulka ovšem navíc obsahuje sloupec index, který určuje pořadí objektů v seznamu. Sloupec index je součástí primárního klíče tabulky, takže seznam připouští duplicitu objektů na různých pozicích v seznamu.

Pro mapy (Map) vzniká tabulka pro třídu vlastníka A, pro třídu hodnot B a pro třídu klíčů C a dále spojovací tabulka, přiřazující trojice A-B-C. Tato tabulka obsahuje primární klíče všech tří tabulek tříd.

Inverzní mapování všech tří kolekcí je přímočaré a do relačních tabulek se promítá stejným způsobem jako oboustranná inverzní relace 1:N. U seznamů navíc v tabulce třídy B (třída prvků seznamu) přibude sloupec obsahující pořadí prvku v seznamu. Je nutno si uvědomit, že toto řešení znemožňuje duplicitu prvků v seznamu – jeden objekt se nemůže současně nacházet na různých pozicích v seznamu. Pokud tuto vlastnost vyžadujeme, je nutno použít normálního mapování. U mapy je v tabulkách hodnot (B) i klíčů (C) obsažen cizí klíč do tabulky vlastníka A).

Dědičnost tříd

V případě odvozených tříd se vytváří v relační databázi tabulka pro každou odvozenou třídu. Mějme dvě třídy definované následujícím způsobem:

public class Osoba
{
    protected String jmeno;
    protected String prijmeni;
    protected int vek;
}
public class Zamestnanec extends Osoba
{
    /** Od kdy je u nás zaměstnán */
    private Date zamestnanOd;
}

V tomto případě vzniknou dvě tabulky – tabulka Osoba se sloupci jmeno, prijmeni a vek a tabulka Zamestnanec s jedním sloupcem zamestnanOd. Navíc má každá tabulka jeden nebo více sloupců obsahujících identifikátor objektu (primární klíč).

Uložíme-li do databáze persistentní objekt třídy Osoba, projeví se jako nový řádek v tabulce Osoba. Uložíme-li objekt třídy zaměstnanec, projeví se jako nový řádek v obou tabulkách – hodnota vlastnosti zamestnanOd se uloží do tabulky Zamestnanec, hodnoty ostatních vlastností se uloží do příslušných sloupců tabulky Osoba, přičemž příslušné řádky v obou tabulkách budou mít stejný primární klíč. Z hlediska čtení objektů z databáze je pak vhodné si uvědomit, že načtení každého odvozeného objektu z relační databáze představuje provedení poměrně složitého dotazu se spojením (UNION) tabulek.

Souhrn

Tímto článkem jsme se dostali na závěr série o JDO. Ačkoli jsme v současnosti svědky takřka neomezené nadvlády relačních databází, vývoj zdá se pomalu ale jistě směřuje k databázím objektovým, i když jejich nástup je zatím poněkud opatrný. Proto, jakkoli dnes může přímé použití JDBC a jazyka SQL v Javovských aplikacích vypadat jako mnohem jednodušší a efektivnější řešení, použití univerzálního rozhraní pro persistenci objektů, které představují Java Data Objects, je investicí jednak do udržovatelnosti celé aplikace a jednak do budoucnosti, kdy bude možné s celou aplikací bezbolestně přejít na efektivní objektovou databázi v okamžiku, kdy se relační databáze odeberou na svoje zasloužené místo v historii informačních technologií.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Žádný příspěvek v diskuzi

Odpovědět