25. srpna 2008

Hibernate - práce s kolekcemi, ManyToMany vazba

S Hibernatem dělám již celkem dlouho, ale i tak pořád narážím na nové a nové věci (to bude asi tím, že jsem manuál k Hibernate celý ještě nečetl a vždy se učím až za pochodu). Teď naposledy jsem řešil celkem intenzivně kolekce a asociace.

Není List jako List

Hibernate z pohledu kolekcí rozlišuje tři základní implementace:
Přesnou definici jednotlivých typů lze najít zde.

Když máme třídu, která obsahuje nějaký seznam (java.util.List), tak to ještě vůbec neznamená, že i Hibernate s tím bude pracovat jako s indexovanou kolekcí. Pro Hibernate se to stává indexovanou kolekcí teprve tehdy, když je definován i index pro jednotlivé položky (@IndexColumn), jinak List Hibernate interně zpracovává jako bags, což může mít nějaké nevýhody, viz další kapitola.

Kromě samotné podoby kolekce je ještě potřeba brát v potaz použitou asociaci mezi třídou s kolekcí a elementy kolekce. Ne všechny případy asociací mohou být z principu indexované (např. obousměrná (bidirectional) ManyToMany vazba). Obecně nelze mít indexované kolekce označené jako inverzní (nastavení inverse="true" v XML konfiguraci nebo použití mappedBy při vytváření asociace pomocí anotací). Přehled typů kolekcí vs. asociací je možné nalézt zde.

One shot delete

Máme kolekci elementů a přidáme nový jeden element. Pokud kolekce není interně interpretována jako indexovaná kolekce, tak pak se změna provádí takto - smažou se všechny elementy a poté se znovu přidají jeden po druhém včetně toho nového. Toto může mít některé negativní dopady, např. pokud potřebujete vést historii všech změn nebo když např. používáme triggery nad vazební tabulkou. Z pohledu kolekcí je nejlepší mít indexované kolekce (viz předchozí kapitola) - pak má Hibernate vše pod kontrolou a když je potřeba přidat jednu položku, tak přidá do databáze pouze jednu položku.

Na druhou stranu je ale někdy žádoucí, aby se smazaly všechny položky a poté znovu přidaly (one shot delete), např. z pohledu výkonnosti, když deset položek chci odebrat a jednu smazat. Toho lze docílit znovu inicializací originální kolekce. Jinak řečeno vytvořím novou kolekci na základě staré.
Nevýhoda indexovaných kolekcí je ta, že při odmazání elementu uprostřed seznamu je nutné upravit indexy všech elementů, které následují.
Více lze opět najít v dokumentaci k Hibernate v kapitole One shot delete.

Práce s obousměrnou ManyToMany vazbou

Mezi objekty ClassA a ClassB je oboustranný vztah ManyToMany, tj. ClassA obsahuje množinu objektů ClassB a naopak. Pokud chceme přidat objekt ClassA do množiny objektů ClassB, pak musíme přidat i objekt ClassB do množiny objektů ClassA.
Z pohledu ukládání obou objektů a jejich kolekcí je potřeba si uvědomit, že nestačí provést uložení pouze nad jedním objektem. Pouze ta strana resp. ten objekt, který je vlastníkem vazby, může uložit změny do databáze.
Podrobné info je v této kapitole.

Otázkou je, zda se lépe nepracuje pouze s jednosměrnou ManyToMany vazbou. Relaci mezi objekty mi drží pouze jeden objekt, takže z pohledu spravování to mám jednodušší. A pokud chci získat i opačný pohled, tak mohu použít tento dotaz (vysvětlení: AssetGroup obsahuje kolekci objektů Asset, vazba mezi oběma objekty je ManyToMany):

SELECT ag
FROM com..AssetGroup ag,
com..Asset a
WHERE a IN ELEMENTS(ag.assets) AND a = :asset

Pořadí práce nad objekty

V nějakém příspěvku jsem našel následující pořadí, v kterém Hibernate vykonává operace nad "svými" objekty:
  1. all entity insertions, in the same order the corresponding objects were saved using Session.save()
  2. all entity updates
  3. all collection deletions
  4. all collection element deletions, updates and insertions
  5. all collection insertions
  6. all entity deletions, in the same order the corresponding objects were deleted using Session.delete()


Tento článek jsem napsal hlavně kvůli sobě, protože zapomínám. Vzvláště s Hibernatem je to znát, protože ten intenzivně používám pouze na začátcích projektů a to zase není tak často.

5 komentářů:

benzin řekl(a)...

Myslim, ze driv nebo pozdeji radeji prejdes na UserType promene a AOP, ktere ti do ni setne DAO, ze ktereho pak danou mnozinu zavolas.

Ma to nekolik vyhod.

Add 1) mas plne v rukou cache i kdyz se jedna o LAZY nacitani.
Add 2) neni problem kdyz se zavre session, protoze si umis otevrit novou. Hodne dobre pri volani z View vrstvy na webu.
Add 3) umi to delat i OneToMeny v jedne tabulce. Tipicky Parent - Children, coz jinak nejde.
Add 4) Kdyz pouzivate anotace, tak neni potreba psat TargetType konkretni tridu, pouze jmeno typu, ktery pak muze byt nastaven v aplikacnim kontextu

Petr Jůza řekl(a)...

Můžes být prosím trochu konkrétnější, nedokážu si moc představit jak to myslíš.

My teď používáme JBoss Envers (psal jsem o něm zde), takže se musím co nejvíce držet Hibernate, aby mi to vše pěkně verzovalo.

benzin řekl(a)...

4Petr: Jednoduse. Vytvoris UserType (normalni vlastnost Hibernate) tento UserType se da nastavit jako typ sloupce.
Dejme tomu, ze mame tabulku Node, ktera Takze si udeljme strukturu

Node ma id jako integer.

Node <1 - *> Node

Definujeme NodeUserType, ktery bude reprezentovan v DB jako integer ale v java vraci jak clasu Node.

Vsechno pro to aby ti to spravne chodilo je dostat do toho UserTypu hibernate dao a pak muzes bez potizi posilat libovolne dotazy. No a hibernate dao do instance UserTypu dostanete pomoci AOP.

Anonymní řekl(a)...

Tééda, to je návrh, benzine, já snad ani nemám slov... Když takhle znásilňuješ Hibernate, není lepší obejít se bez něj úplně?

Anonymní řekl(a)...

Jinak abych přidal něco konstruktivního:

ad 1) tomuto jsem neporozuměl.
ad 2) ono to zavírání session má nějaký důvod, a sice ten, aby se právě zabránilo takovýmhle automatickým loadům a aby všechny operace s databází probíhaly uvnitř transakce. Je otázka, zda je to opravdu v praxi nutné, vzhledem k tomu, že většina aplikací nemá nastavenou dokonalou izolaci transakcí, ale každopádně se to má řešit spíše otevíráním oddělené transakce během renderování view (viz Seam).
ad 3) one-to-many v jedné tabulce samozřejmě udělat jde, a to naprosto standardními prostředky.

ad 4) když používáte anotace a používáte generiky, taky není třeba psát konkrétní třídu.