JDO – identita objektů a dotazy nad databází

15. února 2005

Každý persistentní objekt v objektové databázi má svoji vlastní identitu, je tedy jednoznačně odlišitelný od libovolného jiného objektu nezávisle na hodnotách svých vlastností. Podíváme se, jak lze přidělování identity objektů řídit a jakými způsoby lze k objektům uloženým v databázi přistupovat.

Identita objektů

Jeden ze základních rozdílů objektové databáze oproti relační je dán tím, že objekt, narozdíl od řádku tabulky, má vlastní jedinečnou identitu. Zatímco jednoznačná identifikace řádku tabulky probíhá na základě primárního klíče, tedy uložených hodnot, každý objekt má vlastní identitu nezávisle na hodnotách svých dat.

V případě JDO získává objekt svoji identitu v okamžiku, kdy je učiněn persistentním, tedy v okamžiku, kdy jej implementace JDO začíná vést v patrnosti. Standardem JDO jsou definována dvě schémata přidělování identity objektům:

  • Datastore identity – identitu objektům přiděluje implementace JDO a ta také zajišťuje, aby každý objekt byl jednoznačně identifikovatelný.
  • Application identity – správa identity je v rukou aplikace, veškerá zodpovědnost je na programátorovi.

Každá z obou variant má své výhody i nevýhody. V praxi je možné oba přístupy kombinovat – některé třídy, u nichž existují zvláštní nároky na generování identity objektů, používají aplikační identitu, u ostatních tříd je správa identity ponechána na implementaci JDO.

Datastore identity

Datastore identity je implicitní režim přidělování identity. Ve specifikaci metadat pro konkrétní třídu se projeví takto:

<class name=“Osoba“ identity-type=“datastore“>
    …
</class>

Stejného výsledku také dosáhneme, pokud atribut identity-type vypustíme úplně.

Všechny implementace JDO musí definovat třídu objektů, které budou sloužit jako jednoznačné identifikátory. Například implementace JPOX používá jako identifikátory objekty třídy org.jpox.store.OID. Při vytváření nového persistentního objektu je vygenerován nový jedinečný identifikátor, který je přiřazen objektu. Identifikátor objektu lze získat pomocí volání pm.getObjectId():

Osoba mike = new Osoba(„Mike“, „Oldfield“, 50);
pm.makePersistent(mike);
Object mikeID = pm.getObjectId();

Způsob, jakým jsou identifikátory generovány, je zcela v rukou konkrétní implementace JDO. Některé implementace ovšem umožňují uživatelsky modifikovat generátor identifikátorů podle potřeb aplikace, což je ovšem potřebné ve zcela ojedinělých případech.

Application identity

V režimu aplikační identity je generování jednoznačných identifikátorů na nás. Perzistentní třída, pro kterou chceme používat aplikační identitu, musí obsahovat primární klíč, který jednoznačně identifikuje objekt. Primární klíč může být tvořen jednou datovou položkou (jednoduchý primární klíč), nebo kombinací více položek (složený primární klíč). V praxi se často vytváří primární klíč uměle tak, že objektu přidáme novou vlastnost pojmenovanou například Id a pro každý nový objekt zajistíme generování její jedinečné hodnoty.

Dále musíme definovat novou třídu, která bude použita pro identifikátory. Tato třída může být použita pro identifikátory jedné nebo více persistentních tříd. Metadata pro persistentní třídu s aplikační identitou pak budou vypadat následovně:

<class name=“Osoba“ identity-type=“application“ objectid-class=“IdOsoby“>
    <field name=“ident“ primary-key=“true“>
    <field name=“vek“/>
    …
</class>

V tomto případě je primární klíč tvořen jedinou položkou ident a pro identifikátory se použije třída IdOsoby. Pro identifikátory nelze přímo použít jednoduché třídy jako String nebo Long. Třída použitá pro identifikátory musí totiž splňovat množství požadavků:

  • Musí obsahovat nestatické veřejné vlastnosti stejného jména a typu, jako jsou položky primárního klíče.
  • Musí implementovat metody equals() a hashCode(), které závisí na všech vlastnostech primárního klíče.
  • Musí mít definován implicitní konstruktor (bez parametrů).
  • Musí implementovat konstruktor s parametrem typu String, který vytvoří identifikátor na základě řetězcové reprezentace.
  • Musí implementovat metodu toString(), která vrací řetězcovou reprezentaci, jež může být použita jako parametr konstruktoru.
  • Třída musí být deklarována jako public a musí implementovat rozhraní Serializable.
  • Všechny nestatické vlastnosti třídy musí být rovněž Serializable, doporučují se buď primitivní typy, String, Date nebo Number.
  • Všechny nestatické vlastnosti třídy musí být deklarovány jako public.

Aplikační identita se obvykle používá pouze ve specifických případech, kdy je nutno zajistit, že se identifikátory generují určitým předem daným způsobem (například pokud trváme na tom, že identifikátor osoby bude její rodné číslo). Implementace Triactive JDO například aplikační identitu dosud nepodporuje.

Zpřístupnění objektů v databázi

Dosud jsme pracovali převážně s tím, že jsme objekty vytvářeli a pomocí volání makePersistent jsme je ukládali do databáze. Nyní se budeme věnovat tomu, jak uložené objekty z databáze znovu načíst. JDO standardně poskytuje tyto možnosti:

  • přístup přes identifikátor objektu
  • přístup přes extent
  • dotazy nad objektovou databází

Operace přístupu k objektu nebo objektům v databázích probíhá zpravidla opět v rámci transakce. Objekty jsou typicky načteny jednou z výše uvedených metod a ocitají se tak v aplikaci ve stavu hollow. Poté dochází buď ke čtení, nebo změně jejich vlastností, přičemž se stávají persistent clean respektive persistent dirty. V okamžiku úspěšného ukončení transakce se případné změny promítají zpět do úložiště objektů a objekt se vrací do stavu hollow.

Přístup přes identifikátor

Přístup přes identifikátor lze použít v případě, že jsme někde získali jednoznačný identifikátor objektu, například pomocí volání pm.getObjectId(), jak jsme si ukázali výše. Mnohem častěji se tato metoda využívá v případě, že identifikátory objektů generujeme sami a jsme proto schopni vytvořit identifikátor požadovaného objektu (například pomocí rodného čísla osoby). Předpokládejme, že v proměnné ident máme uložen identifikátor požadovaného objektu. Potom lze načíst z databáze objekt s tímto identifikátorem (pokud existuje) následujícím způsobem:

Transaction tx = pm.currentTransaction()
tx.begin();
Osoba osoba = (Osoba) pm.getObjectById(ident, true);

/* práce s objektem (čtení dat, změny) */

tx.commit();

Metoda getObjectById() má dva parametry. První je příslušný identifikátor požadovaného objektu, druhý parametr validate je typu boolean. Pokud má hodnotu false a instance objektu se stejným identifikátorem se již nachází v paměti nebo vyrovnávací paměti úložiště, nekontroluje se přítomnost objektu v samotném úložišti. Při nastavení true se vždy kontroluje přítomnost objektu v úložišti.

Přístup přes extent

Přístup přes extent jsme již naznačili, nyní se k němu vrátíme poněkud podrobněji. Takzvaný extent třídy je množina všech instancí (objektů) dané třídy uložených v objektové databázi a může nebo nemusí zahrnovat i třídy odvozené z dané třídy. Typický přístup k objektům přes extent může vypadat následovně:

Extent osoby = pm.getExtent(Osoba.class, true);
Iterator itr = osoby.iterator();
try
{
    while (itr.hasNext())
    {
        osoba = (Osoba) itr.next();
        /* … zpracuj data … */
    }
}
finally
{
    employees.close (itr);
}

V tomto případě jsme se při čtení dat obešli bez použití transakce. Pokud ale data v objektech modifikujeme, je opět vhodné před započetím cyklu začít novou transakci pomocí tx.begin() a po zpracování všech objektů ji ukončit pomocí tx.commit, aby se všechny změny projevily konzistentně.

Metoda pm.getExtent() má dva parametry, z nichž první je objekt typu Class reprezentující nějakou persistentní třídu (obvykle získáme pomocí Třída.class) a druhý parametr typu boolean určuje, zda se do extentu zahrnou i objekty odvozených tříd či nikoli.

Dotazy nad databází

Dosud popsané metody přístupu k objektům představují dva opačné extrémy – ve většině případů neznáme přesný identifikátor objektu, na druhou stranu ale obvykle nepožadujeme všechny objekty dané třídy, ale pouze ty, které splňují určitá kritéria. Podobně jako u relačních databází se vžil jazyk SQL pro formulování strukturovaných dotazů, JDO používá dotazovací jazyk JDOQL (JDO Query Language), který umožňuje specifikovat množinu objektů uložených v databázi na základě různých kritérií.

Jazyk JDO je založen na jazyce Java a syntakticky je téměř totožný. Dotaz vždy formulujeme pro konkrétní třídu objektů, přesněji řečeno pracujeme vždy nad extentem konkrétní třídy ze kterého provádíme výběr. Při definici dotazu můžeme přistupovat ke všem persistentním položkám dané třídy a můžeme využívat klíčové slovo this. Dále má jazyk JDQL tyto vlastnosti:

  • Dotaz dále může obsahovat číselné literály a řetězcové literály zapsané v jednoduchých nebo dvojitých uvozovkách.
  • Je možno porovnávat hodnoty primitivních datových typů, objektů odpovídajících „obalovacích“ tříd (Integer, Byte, Character a podobně) a třídy Date.
  • K porovnávání se používají standardní relační operátory, pro rovnost se používá operátor ==, nikoli metoda equals().
  • Operátory přiřazení nejsou podporovány.
  • Výsledkem porovnání s hodnotou null je vždy false, přitom nedochází k výjimce.

Není možné používat žádné metody tříd kromě následujících:

String.startsWith(String)
řetězec začíná určitým prefixem
String.endsWith(String)
řetězec končí určitým suffixem
Collection.isEmpty()
kolekce je prázdná
Collection.contains(hodnota)
kolekce obsahuje prvek s danou hodnotou
Map.isEmpty()
asociativní pole je prázdné
Map.contains(hodnota)
asociativní pole obashuje prvek s danou hodnotou

Tato množina metod daná standardem JDO 1.0.1 je poměrně omezená, jednotlivé implementace proto často umožňují použití dalších metod, jako například vyhledávání podřetězců v řetězcích, znaků a podobně. Budoucí standard JDO 2.0 již ve svém návrhu obsahuje poněkud širší repertoár použitelných metod.

S dotazy v jazyce JDOQL pracujeme pomocí třídy javax.jdo.Query. V nejjednodušším případě lze dotaz zapsat následujícím způsobem (pm je opět dříve vytvořený objekt třídy PersistenceManager):

Query query = pm.newQuery(Osoba.class, „vek < 55“);
Collection vysledky = (Collection) query.execute();

Jedná se o jednoduchý dotaz, který vybírá z databáze všechny objekty třídy Osoba, které mají v položce vek hodnotu menší než 55. Voláním query.execute() dotaz provedeme a výsledkem je kolekce objektů, které vyhovují dané podmínce. Metoda pm.newQuery() má v tomto případě dva parametry – první je třída objektů, mezi kterými vyhledáváme a druhý parametr je takzvaný filtr, neboli výraz v jazyce JDQL, který specifikuje vlastnosti hledaných objektů. K dispozici je více variant této metody, filtr je například možno zadat odděleně pomocí volání pm.setFilter(filtr).

Jako v předchozích případech, i v případě dotazů probíhá obvykle celý dotaz v rámci jedné transakce. Pořadí operací je tedy typicky začátek transakce, vytvoření dotazu, vykonání dotazu, zpracování výsledků (projdeme a zpracujeme prvky kolekce, která je výsledkem dotazu) a ukončení transakce.

Předchozí článek andulky
Další článek E-mail na webech
Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *