Zobrazují se příspěvky se štítkemHibernate. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemHibernate. Zobrazit všechny příspěvky

18. září 2011

Proč nechci použít Hibernate na dalším projektu

Minulý týden se mě kolega zeptal, zda bych použil znovu Hibernate na dalším projektu? Já jsem mu po krátkém zamyšlení řekl, že již ne, že bych Hibernate (a obecně žádné jiné ORM řešení) nepoužil. Následovala diskuze, kde jsem se snažil obhájit mojí odpověď:

  • ORM nástroje se snaží vývojáře odstínit od konkrétního úložiště dat, snaží se vývojáře držet pouze v objektovém světě bez ohledu na to, že nejčastěji ukládáme data do relačních databází. Je naivní si myslet, že napíši kvalitní aplikaci bez znalostí databází a SQL.

  • ORM nástroje (pozn. konkrétně Hibernate, protože s ním několik let pracuji, ale myslím si, že to lze zobecnit pro všechny ORM nástroje) přidávají do projektů další velkou složitost, nároky na znalosti a zkušenosti. Toto má vliv na spoustu dalších věcí - výběr vhodných lidí do týmu, architektura systému, větší pravděpodobnost chyb, plnění termínů atd.

  • znalost SQL mi přijde natolik rozšířená a základní, že v tomto nevidím další znalostí zátěž pro vývojáře

  • používání ORM odstiňuje vývojáře od přímé práce s databází, což často vede k tomu, že vývojáři vůbec vlastně nevědí, jak jsou určité operace náročné, kolik se vlastně výsledně musí zavolat SQL dotazů, aby se jedna Hibernate operace vykonala

  • Hibernate přináší do vývojového týmu prvek náhodnosti, špatné říditelnosti - pokud vím, že mám napsat 3 dotazy přes JDBC, tak je to tak jasná a ohraničená věc, že není co dalšího řešit. Místo toho implementovat DAO pomocí Hibernatu, to moc jasné být nemusí - nikdy člověk neví, na čem se může zaseknout nebo jak se bude Hibernate v určitých kritických stavech chovat.

  • při porovnávání JDBC vs. ORM přístupů je potřeba také zmínit, že databáze často mnohonásobně přežijí aplikace, které je využívají. Technologie pro server a klienta se mění mnohem rychleji než relační databáze a jazyk SQL. Z tohoto pohledu je základem správný návrh databáze bez ohledu na to, jakou architekturu pak aplikace bude mít.

  • kolikrát se na projektu měnila databáze? Možná lepší otázka - stalo se již někomu, že se během projektu měnila databáze? Když pominu vývoj aplikací pro více databází, tak naopak si myslím, že napojení na konkrétní databázi mi nabízí mnohem více než když jsem odstíněný přes ORM.

  • ladění složitějších HQL nebo Criteria dotazů moc nejde a já osobně jsem vždy skončil u SQL. Někdy postupuji i naopak - pomocí SQL si vyzkouším, že daný dotaz je možné vytvořit, a že je správný a pak ho přepisuji pomocí Hibernatu.

Myslím, že nejsem sám, kdo má dnes podobný názor k této problematice jako já, viz např. článek Problems with ORMs jako jeden z nich.

Základní otázkou ale zůstává, co tedy jiného místo toho? Mě zde bohužel chybí osobní zkušenost, ale určitě bych se vydal cestou přes JDBC, tedy jako vhodné řešení mi přijde použití MyBatis nebo Spring JDBC nebo Spring Data nebo to celé nějak zkombinovat dohromady.

15. února 2011

Znáte Spring Data (JPA)?

Již jsem o tom psal na Twitteru, ale myslím, že si to zaslouží trochu větší a delší pozornost, tak to píši ještě sem.

Pod hlavičkou firmy SpringSource se v poslední době objevilo spoustu nových projektů a jedním z nich je i projekt Spring Data. Rozsah projektu je celkem velký:

The primary goal of the Spring Data project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services. A secondary goal is to provide additional support for relational database technologies such as Oracle RAC and convenience classes for Java generic based repository classes.


Mě osobně nejvíce zaujal podprojekt Spring Data JPA, o kterém vyšel pěkný úvodní článek na blogu.

Snad každý si vytvořil nějaké svá rozšíření pro práci s Hibernatem nebo JPA, aby nemusel pořád dokola psát jednoduché dotazy na načtení jednoho objektu, všech objektů, jejich uložení atd. Právě toto vše a mnohem více nabízí tento projekt Spring Data JPA, takže budu moci s klidem zahodit své řešení a začít využívat toto od Springu.

9. listopadu 2010

Hibernate: rozdílné výsledky HQL a Criteria API?

Již je to nějaký čas, co jsem řešil problémy s Hibernate dotazy a v rámci ladění jsem už zkoušel všechno možné i nemožné a podařilo se mi, že jsem měl "stejné" dva dotazy, ale každý vracel jiné výsledky.

První dotaz je napsán pomocí Criteria API a v dané úloze správně nevrátil žádnou hodnotu. Druhý dotaz je napsán pomocí HQL a špatně najde jeden záznam (jednoho poplatníka).


Criteria criteria = createCriteria();
   
criteria.add(Restrictions.gt(DOaa_odpad_poplatnik.PLATNOST_OD, param.getDatum_posl_aktualizace_aa()));
    
List list1 = findByCriteria(criteria);

    



String hql = "select poplatnik from " + DOaa_odpad_poplatnik.class.getSimpleName() + " poplatnik "
        
+" where platnost_od > :datum";
    
Query query2 = createQuery(hql);
    
query2.setDate("datum", param.getDatum_posl_aktualizace_aa());
    
List list2 = query2.list();

Musím se přiznat, že do teď netuším, jak je to možné. Pokud někdo víte, budu rád, když se přiučím ...

24. ledna 2010

Hibernate - řazení NULL hodnot

Dnes výjímečně nebudu publikovat svůj příspěvek, ale příspěvek mého současného kolegy Vaška Hrdiny. Řešený problém mi přišel natolik zajímavý, že jsem ho požádal o publikaci na mém blogu.

Řazení v Hibernate

Pro řazení přes Criteria API existuje metoda addOrder():
crit.addOrder(Order.asc(DOdatovy_objekt.POLE)); //vzestupně
crit.addOrder(Order.desc(DOdatovy_objekt.POLE)); //sestupně
pro HQL je možné použít klauzuli order by:
createQuery("from " + DOdatovy_objekt.class.getSimpleName() +  " as do order by do." + DOdatovy_objekt.POLE); //vzestupně 
createQuery("from " + DOdatovy_objekt.class.getSimpleName() +  " as do order by do." + DOdatovy_objekt.POLE + " desc"); //sestupně

Řazení NULL hodnot

Problém ale nastává pro řazení NULL hodnot. Pokud je políčko NULL, pak jej MSSQL řadí na první místo, Oracle oproti tomu jako poslední. Ne vždy můžeme použít řazení až na aplikačním serveru (např. při tisku velké sestavy, kdy musíme získávat data stránkovaně), přesto je třeba řadit na všech DB stejně.
Nepřišel jsem na vhodný způsob, jak toto vyřešit, hibernate JIRA obsahuje požadavek, který se tohoto týká. Jediný způsob, na který jsem byl schopný přijít je řešení přes formule.

Jedná se o to, že do mapovaného objektu přidáte políčko (stačí políčko, nemusí mít ani getter), které se naplní předem definovaným SQL příkazem (pozor, jedná se skutečně o SQL, je tedy třeba hlídat mezidatabázovou kompatibilitu). Podle něj se dá řadit. A pokud zajistíte, aby se v políčku vyskytovaly takové znaky, aby se řadilo podle Vašich potřeb (typicky NULLy půjdou jako první), je vyhráno.

Následující příklad ukazuje, jak řadit písmeno orientačního čísla tak, aby pokud není vyplněné, bylo první (tedy např. 16, 16a, 16b...):
@Entity
@Table(name = "adresa")
public class DOadresa extends cz.marbes.daisy.modules.x.mdo.DOGadresa {

@Formula("case when PISMENO_CO is null then ' ' else PISMENO_CO end")
private String pismenoCONotNull;
}

V kódu, který potřebuje takto řadit, pak stačí uvést:
crit.addOrder(Order.asc("pismenoCONotNull")); //pro Criteria API
createQuery("from " + DOdatovy_objekt.class.getSimpleName() + " order by pismenoCONotNull"); //pro HQL

Políčko pismenoCONotNull je pak normálně přístupné, jako kterékoliv jiné políčko, obsahuje buď mezeru nebo obsah pole PISMENO_CO.

25. září 2008

Proč JPA, když mi stačí Hibernate?

Většinu projektů jsem v minulosti realizovat pomocí "čistého" Hibernatu (Hibernate Core). Na posledním projektu jsem byl nucen přejít na Hibernate Entity Manager resp. JPA z důvodu použití nástroje JBoss Envers na verzování. Sice to byl pádný důvod proč přejít na JPA, ale pokud bych tento důvod neměl, tak jina žádný důvod pro přechod k JPA nemám, spíše naopak.

Než si začnu stěžovat, tak ještě musím uvést, že píši v kontextu lightweight aplikací. Při použití EJB resp. aplikačních serverů je to zcela jiné, tam vidím velký význam a přínos ve standardizaci rozhraní datové vrstvy.

Zde jsou některé mé stížnosti:

  • JPA toho zdaleka neumí tolik co samotný Hibernate (viz list of all Hibernate extension annotations nebo Hibernate Annotation Extensions).

  • Na předchozí bod je možné namítnout, že mi nic nebrání, abych tyto rozšíření používal. To je pravda, ale proč pak nepoužít přímo Hibernate? Jedna z uváděných vlastností JPA je ta, že je možné vyměnit ORM engine pokud se budu držet JPA specifikace. Má to vůbec smysl? Občas potřebuji vyměnit databázi, ale nevím, kdy bych potřeboval vyměnit ORM engine.

  • Nedávno jsem řešil problém s ukládáním souborů (BLOB objektů) do databáze. Spring nabízí jako vždy pomoc - třída AbstractLobType a její implementace (hezký popis je na tomto blogu). Zde je ovšem podmínkou konfigurovat Hibernate pomocí LocalSessionFactoryBean, tedy používat "čistý" Hibernate Core.

  • Konfigurace Hibernatu přes JPA je trochu přes ruku. Většina věcí se musí konfigurovat přes properties v persistence.xml. Je ale pravda, že nyní (všimnul jsem si toho až v poslední verzi 3.4 Entity Manageru) je možné používat JPA a konfigurovat Hibernate přes standardní hibernate.cfg.xml soubor, viz hibernate.ejb.cfgfile property.

  • Stejnou výtku jako v předešlém bodě mohu mít ke psaní JPQL dotazů. Samotný JPQL sice vychází z HQL, ale takové možnosti nemá. Mohu zase používat HQL, mohu využívat Session, nativní JDBC dotazy, ale vše je to přímočařejší s Hibernate bez JPA.

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.

10. července 2008

Verzování entit - JBoss Envers

Sledování historie změn není nijak výjimečný požadavek, a proto mě i celkem překvapuje, že na tomto poli nejsou (nebo jsem nenašel) skoro žádné open-source projekty, které by toto řešily. Jeden jsem však našel a jmenuje se JBoss Envers.

Nemá cenu opisovat, to co je uvedeno na webu projektu, jen bych zase uvedl pár poznámek.

Envers potřebuje pro svoji práci Hibernate a Hibernate Entity Manager.

Nyní se projekt nachází ve verzi 1.0.0.Beta2 - kromě verzování atributů entit zvládá základní vazby typu @OneToOne, @OneToMany a @ManyToOne. Zatím neumí (mělo by být ve verzi 1.1.0) takové vazby, kde je automaticky vytvořena vazební tabulka (join table), tedy např. @ManyToMany, ale mohou to být třeba i vazby @OneToMany. Do jisté míry to lze obejít tak, že si vytvořím vlastní vazební entitu a tím pak budu mít pouze vazby @OneToMany nebo @ManyToOne.

Velice příjemné je, že lze vytvořit ANT task, který umí generovat datové schéma včetně tabulek pro verzování a pro evidenci revizí.


Další zdroje:

27. srpna 2007

Hibernate Criteria API vs. HQL

Nedávno jsem dělal docela složitou stránku pro vyhledávání položek uložených v databázi, kde si uživatel mohl vybrat velké množství omezujících parametrů (přibližně asi 25 položek). Navíc jednotlivé objekty mají velké množství atributů a já potřeboval vypsat pouze nepatrné procento z nich.

Mám rád Hibernate Criteria API pro vytváření dynamických dotazů, protože nemusím skládat výsledný dotaz z řetězců, nemusím se starat o správné typy apod. Prostě celkově mi to přijde více "programovější" využívat dostupné Criteria API (pozn. celkem zajímavý článek o srovnání Criteria API a HQL vyšel na DevX). V tomto případě jsem ovšem narazil a musel jsem to nakonec celé udělat pomocí HQL dotazů :(.

Narazil jsem na následující problémy s Criteria API:

  1. nebyl jsem schopen efektivně omezit množinu atributů příbuzných objektů, které jsem potřeboval vrátit

  2. nemohl jsem se dotazovat na kolekci elementů

Nemožnost omezení atributů

Přes Criteria API se standardně načítají všechny atributy pro danou třídu včetně příbuzných tříd. Je možné toto změnit pomocí tzv. projekcí. Tyto projekce mají však svoje omezení - není možné provést projekci pouze na určitý atribut příbuzného objektu (=related object). Takže například když mám hlavní objekt Dokument s vazbou na lokalitu, tak nejsem schopen provést takovou projekci, aby se mi vrátil pouze název lokality.
    List results2 = session.createCriteria(Document.class)
.setProjection( Projections.projectionList()
.add( Property.forName("name") )
.add( Property.forName("location.name") )
)
.list();

Kolekce elementů

Na začátek bych měl asi nejdříve vysvětlit, co to je kolekce elementů. Vysvětlím to na příkladě. Mám dokument a k němu se mi vážou kódy lokalit. Tedy jeden dokument má na sebe vázanou kolekci kódů lokalit. Zde se nejedná o kolekci objektů, ale pouze elementů s jejich kódem. Určitě se zeptáte proč to není kolekce standardních objektů? Je to proto, že informace o jednotlivých lokalitách jsou uloženy v jiném systému a v našem systému ukládáme pouze identifikátory lokalit, abychom se na ně mohli později dotazovat. Konkrétně pro dokument konfigurace pomocí anotací vypadá následovně:
  @CollectionOfElements(fetch = FetchType.LAZY)
@JoinTable(name="document_pagis", joinColumns = @JoinColumn(name = "doc_id"))
@Column(name = "idob_pg")
private Set paGisCodes;
Je nutné poznamenat, že kolekce elementů není součástí standardního JPA, ale je to rozšíření Hibernate. Rád bych se k problematice kolekcí elementů ještě vrátit v některém dalším příspěvku.

Zpět k omezení Criteria API. V současné verzi Hibernate (3.2.3) nejsem schopen přistupovat k těmto elementům přes Criteria API. Tuto informaci jsem našel přímo na stránkách Hibernate, ale nyní bohužel nejsem schopen to znovu najít, abych uvedl přímo odkaz.

Z výše uvedených důvodů jsem vyhledávání implementoval přes HQL bez žádných větších problémů. Jen z toho nemám tak dobrý pocit, jako kdybych použil Criteria API :).