Hlavní navigace

Programování v JavaFX: Hibernate ORM, úvod a základní konfigurace

21. 1. 2016
Doba čtení: 14 minut

Sdílet

Minulý díl by věnován aktualizacím a ukládání nových záznamů s pomocí projektu JOOQ. Také jsme si ukázali některé obecné možnosti projektu, a tím jsme ukončili část o JOOQ. V dnešním dílu zahájíme kapitolu o ORM Hibernate a ukážeme si jeho konfiguraci a jednoduché zobrazení dat z tabulky do konzole.

Dnešním dílem se tedy dostáváme k projektu, který je vlastně jakýmsi synonymem pro ORM. Jak to ukazuje odkaz na stránky projektu Hibernate.org, nejedná se o jeden jediný projekt, ale dokonce o pět různě rozsáhlých projektů:

  1. ORM – ten nás bude zajímat nejvíce, jak už napovídá jeho název
  2. Search – full-text vyhledávání pro doménové modely
  3. Validator – omezení založené na anotacích
  4. OGM – Object/Grid Mapper – vlastně ORM pro NoSQL databáze
  5. Tools – řádkové i grafické nástroje pro využití v aplikacích

Nás bude samozřejmě zajímat hlavně ORM, takže přejdeme na příslušnou stránku Hibernate ORM

Jak je z obsahu patrné, v levé části jsou hlavně odkazy na stažení, dokumentaci a další možnosti, které framework nabízí. V horní části je pak opět přímá možnost stažení potřebných souborů a v části střední stručný popis vlastností. Jak je ze stránek jasné, poslední stabilní verzí je 5.0.5, se kterou budeme v našem seriálu pracovat. Proto zde také odkážeme na hodně rozsáhlou dokumentaci: Dokumentace ORM 5.0

Více zatím není nutné výše uvedené informace rozebírat, a tak se raději pustíme do samotné práce a ukážeme si, co nám Hibernate může přinést. Než to ale budeme moci udělat, musíme si připravit naši ukázkovou aplikaci:

  1. v JFXSB si otevřeme minulou verzi (5) XML souboru a uložíme jí pod novým názvem samExam6.fxml do adresáře GUI-Files
  2. v nové verzi změníme název kontroléru na jfxapp.samexam6 a uložíme změnu
  3. vytvoříme a uložíme si do schránky kostru kontroléru verze FULL
  4. vyzkoušíme náhled na nově vytvořený formulář – viz první obrázek v první galerii
  5. otevřeme si IJI a vytvoříme novou třídu samexam6 a vložíme do ní kód z kostry kontroléru
  6. do hlavního formuláře s rozcestníkem přidáme volání této nové procedury a změníme při tom všechna potřebná nastavení
  7. zkušebně spustíme aplikaci a nový formulář otevřeme – viz druhý obrázek první galerie

Aplikaci již máme připravenou, a tak se můžeme pustit do dalších kroků, které nás dovedou k funkční ukázce možností Hibernate ORM. Jak bude z dalšího textu jasně patrné, je tento framework zdaleka nejsložitější na přípravu oproti tomu, co jsme poznali v minulých dílech. Začneme tedy tím, že si z hlavní stránky stáhneme komprimovaný soubor s aktuální verzí frameworku hibernate-release-5.0.5.Final.zip a rozbalíme ho do libovolného adresáře. Zde se můžeme přesvědčit, že projekt obsahuje větší množství součástí. Pro nás je ale zajímavý pouze adresář ../lib/required, kde je obsaženo celkem 8 souborů – viz třetí obrázek v první galerii. Všechny tyto soubory překopírujeme do aplikačního adresáře libs – viz čtvrtý obrázek první galerie.

To ale ještě není z pohledu potřebných externích knihoven všechno a pokud bychom se snažili s naší aktuální sestavou knihoven zahájit činnost s Hibernate ORM, tak bychom celkem tragicky neuspěli. Celá záležitost je docela nepříjemná proto, že chybové hlášení není zrovna moc vypovídající a chvíli trvá, než se najde náprava. Proto nebudeme nic podobného riskovat a zaměříme se na projekt JTA – Java Transaction API. Na této stránce Jta.jar si najdeme položku č. 5 (verze 1.3.1), stáhneme příslušný komprimovaný soubor a do aplikačního adresáře libs rozbalíme jediný zde obsažený soubor jta-1.3.1.jar. Jak bylo možné si na stránce se stažením potřebného souboru všimnout, existuje celá řada verzí. To ještě více komplikuje případnému zájemci život, a tak jenom konstatujeme, že námi vybraná verze v aktuální konfiguraci prostě funguje…

Externí knihovny máme připravené a můžeme pokročit dál. A uděláme to trochu zvláštně – vrátíme se k názvu použitého frameworku a znovu se zaměříme na obsah jeho zkratky: ORM = Object/Relational Mapping. Pokud z tohoto názvu vyjdeme, tak je jasné, že musíme vytvořit nějakou novou konstrukci. Tato konstrukce by měla zajistit, že údaje obsažené v relační databázi (konkrétně naše ukázková tabulka udaje) se pomocí nějakých nástrojů „namapuje“ na objekty v naší aplikaci JavaFX. Pokud by se někdo chtěl do této problematiky hlouběji ponořit, tak zjistí tři zásadní věci:

  • přímo na stránkách projektu Hibernate i jinde je k nalezení spousta návodů, informací, příkladů atd.
  • celá problematika je poměrně složitá a není jednoduché se v ní hlavně pro začátečníka orientovat
  • není vůbec zaručeno, že nalezená informace či návod budou použitelné pro danou verzi Hibernate a dalších součástí případného uživatelského projektu

Náš seriál není místem, kde bychom mohli a měli podrobně rozebírat nějaké složité a sofistikované náležitosti, takže si ukážeme pouze jednoduché, ale plně funkční řešení. Pro začátečníky je to dostatečné a dává každému možnost se problematice věnovat podrobněji, pokud by to považoval za nutné. Naše konstrukce se tedy bude skládat ze tří součástí, které jsou k funkci bezpodmínečně a vždy nutné. Pouze si je velmi zjednodušíme pro naše konkrétní účely. Funkční systém ORM se bude skládat ze dvou souborů a jedné aplikační třídy:

  1. konfigurační XML soubor s informacemi o připojení k databázi, většinou jej najdeme pod názvem hibernate.cfg.xml. Je možné použít i jiný název, ale je nutné to v aplikaci zohlednit
  2. XML soubor, který propojuje konfigurační soubor s aplikační třídou pro jednotlivé části (v našem případě tabulku). Název je individuální, běžně používaná přípona je XXX.hbm.xml
  3. aplikační třída, která obsahuje příslušný konstruktor a sestavu getterů/setterů. Název je individuální a je to běžná forma aplikační třídy

Jak to už někdy bývá, my začneme odzadu a jako první si ukážeme jednoduchou variantu aplikační třídy. Na začátek budeme pouze bez dalšího vysvětlení konstatovat, že její kód můžeme celý uživatelsky vytvořit (pro naší jednu jednoduchou tabulku by to nebyl vůbec žádný problém) nebo se zaměřit na možnosti její automatické (nebo spíše poloautomatické) generace. Vzhledem k tomu, že se Hibernate používá hodně často a intenzivně, jsou k dispozici dva základní balíky s příslušnými nástroji. Již na stránce s projekty si bylo možné v sekci Tools povšimnout, že je k dispozici nástroj JBoss Tools. Ten sice obsahuje i další nástroje (viz JBoss Tools), ale Hibernate Tools jsou klíčovou položkou. JBoss Tools je možné použít jako plugin do velmi populárního IDE Eclipse. Druhou možností je použít fork Eclipse od RedHat – Red Hat JBoss Developer Studio.

JBoss Tools jsou pak přímou součástí tohoto IDE a je možné je využít. Obecně lze říci, že JBoss Tools umožní generovat všechny tři potřebné mapovací „součástky“ a to dokonce pomocí reverzního inženýrství z příslušných tabulek. Při práci s nějakou rozsáhlejší aplikací s větším množstvím tabulek, pohledů a dalších náležitostí je možné JBoss Tools jednoznačně doporučit. A to bez ohledu na to, jestli je pro vývoj aplikace použito Eclipse nebo jiné IDE! Druhou možností je pak využití dalšího projektu, který si ukážeme v našem seriálu. Jedná se projekt Projekt Lombok. Ten dokáže vytvořit obsah aplikační třídy a my si práci s ním ukážeme hlavně proto, že IJI obsahuje plugin s tímto nástrojem. Je samozřejmě možné projekt využít i v Eclipse a NetBeans: Lombok Download.

My nic stahovat nemusíme a ukážeme si, jak do IJI přidat nějaký plugin na konkrétním příkladě. V IJI si tedy otevřeme menu File->Settings->Plugins. Celkový náhled je vidět na pátém obrázku v první galerii. Jak je z obrázku patrné, volíme další akci pomocí tlačítka Browse repositories… Šestý obrázek první galerie nám ukazuje, jak vypadá následný formulář včetně zadaného názvu pro hledání pluginu. V dalším kroku samozřejmě použijeme zelené tlačítko Install. Pak proběhne instalace pluginu a po jejím ukončení nás systém vyzve k restartu IJI – viz poslední obrázek v první galerii.

Vše už je připravené k tomu, abychom mohli zahájit vytvoření aplikační třídy. Ručně si musíme vytvořit základní kostru:

package jfxapp;

import java.sql.Date;

public class udaje {

    private Short id;
    private Integer celecis;
    private double descis;
    private double maledes;
    private String retezec;
    private Date datum;

    public udaje() {} }

Malé písmeno v názvu třídy použijeme proto, že již máme třídu s podobným názvem v aplikaci. Mohli bychom samozřejmě použít i úplně jiný název, není to nijak limitováno. Ještě jedna poznámka k hlavičce třídy, někdy je možné najít i tuto variantu:

public class udaje  implements java.io.Serializable {

Pro nás ale není úplně nutná a navíc se doporučuje s tímto nastavením zacházet velmi opatrně. Více informací je možné získat např. zde Java Serialization.

Hlavičku třídy následuje soubor deklarací jednotlivých položek tabulky. Můžeme si povšimnout, že jsme zachovali typ klíčové položky, ale v celočíselných položkách použili standardní typ proměnné. V tomto případě je to samozřejmě možné, protože se tyto vlastnosti negenerují, ale zadávají. Pak následuje příkaz, který zajistí konstruktor pro vnitřní třídu. Tím máme vše, co je od nás očekáváno k dispozici a můžeme přejít ke generování dalších řádků kódu. Kurzor si ve třídě nastavíme tak, aby byl až za deklarací konstruktoru a klikneme pravým tlačítkem na plochu. Objeví se nám nabídka možností a my zvolíme položku Generate… nebo přímou zkratku Alt+Insert. Jak ukazuje první obrázek ve druhé galerii, objeví se další nabídka s celkem devíti volbami. Nás ale budou zajímat pouze dvě, a tak si vybereme hned první z nich – Constructor. Následně se objeví přehled polí, pro které je možné konstruktor generovat – viz druhý obrázek druhé galerie. Pro úspěšnou akci musíme vybrat všechny nabízené položky, jak to ukazuje třetí obrázek ve druhé galerii, a kliknout na tlačítko OK. Do třídy se přidá následující kód:

public udaje(Short id, Integer celecis, Double descis, Double maledes, String retezec, Date datum) {
        this.id = id;
        this.celecis = celecis;
        this.descis = descis;
        this.maledes = maledes;
        this.retezec = retezec;
        this.datum = datum; }

Zde nemá smysl žádný komentář, a proto z voleb vybereme druhou položku Getter and Setter, vybereme všechny nabízené položky (viz čtvrtý a pátý obrázek ve druhé galerii) a dostaneme pro všechna pole následující kód (uvádíme pouze ukázku jedné položky, ostatní jsou stejné s výjimkou názvu a typu proměnné):

public Short getId() { return id; }
public void setId(Short id) { this.id = id; }

Tím je aplikační třída hotová a do přílohy dáváme její kompletní kód: udaje.java. Další součást, kterou musíme vytvořit, je „propojovací/mapovací“ soubor, který se odkazuje na jedné straně na aplikační třídu a na straně druhé na konfigurační soubor. Tím je vlastně uskutečněno mapování objektů v aplikaci a databázi. Informace o jeho obsahu v souvislosti s aplikační třídou můžeme najít třeba zde: Hibernate Mapping Files

My si musíme samozřejmě přizpůsobit mapovací soubor aktuálním podmínkám a pak může vypadat třeba takto (uvedeme pouze hlavní položky, bez hlavičky XML souboru. Celý soubor je pak v příloze udaje.hbm.xml):

<hibernate-mapping>
    <class name="jfxapp.udaje" table="udaje">
        <id name="id" type="short" column="id"/>
            <property name="celecis" column="celecis" type="int"/>
            <property name="descis" column="descis" type="double"/>
            <property name="maledes" column="maledes" type="double"/>
            <property name="retezec" column="retezec" type="string"/>
            <property name="datum" column="datum" type="date"/>
    </class>
</hibernate-mapping>

Jak je z kódu patrné, skutečně se zde provádí propojení/mapování aplikační třídy a tabulky včetně všech položek jak v tabulce, tak v Getterech/Setterech. Aplikační třída i mapovací soubor mohou mít samozřejmě mnohem složitější strukturu včetně třeba způsobu vytváření klíčových položek, propojení tabulek atd. Zájemci mohou na netu najít dostatek informací a příkladů, pokud by chtěli nebo potřebovali využít více možností mapování (Collection Mapping, Basic O/R Mapping, atd.). Pro naše účely bude tento jednoduchý příklad dostatečný, a tak se můžeme pustit do posledního úkolu – vytvoření konfiguračního souboru. Podrobné informace o jeho možnostech a základní jednoduchou kostru můžeme najít třeba zde: Hibernate Configuration. Položky opět přizpůsobíme našim potřebám a požadavkům. V textu uvedeme pouze výkonnou část souboru, celý je pak v příloze hibernate.cfg.xml.

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
        <property name="hibernate.connection.url">jdbc:postgresql://127.0.0.1:5432/fxguidedb</property>
        <property name="hibernate.default_schema">public</property>
        <property name="hibernate.connection.username">fxguide</property>
        <property name="hibernate.connection.password">fxguide</property>
        <property name="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</property>

        <mapping resource="udaje.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

Jak je z kódu zřejmé, jsou zde definovány základní požadavky pro připojení vybrané databáze, jako je typ databázového konektoru, nastavení JDBC k vybrané databázi, databázové schéma a uživatelské jméno a heslo. Také je definován databázový dialekt a k němu uvedeme jednu stručnou poznámku: ve většině příkladů je uváděn ve formátu org.hibernate.dialect.Pos­tgreSQLDialect. To ale nemusí být vždy to pravé ořechové, a tak se vždy doporučuje zadávat tento příkaz ručně a nechat si od IJI napovědět, který dialekt je v knihovně aktuálně k dispozici a který se navíc hodí pro aktuální verzi PG! Poslední řádek pak odkazuje na mapovací soubor (v případě využití více tabulek zde musí být všechny, které chceme do aplikace zařadit). Poslední krok, který nám při tvorbě základu ORM ještě zbývá, je uložení vytvořených mapovacích souborů. Uložení aplikační třídy je jasné a není třeba nad ním nijak dlouze přemýšlet. O obou XML souborů to ale tak jasné není a mohli bychom zbytečně narazit na problém s jejich „neviditelností“ pro aplikaci. Proto to vyřešíme jednoduše (a pro větší množství propojovacích souborů i velmi přehledně): do aplikace přidáme nový adresář Hibernate a přiřadíme mu status Resources v nastavení vlastností projektu. Pak do něj překopírujeme nebo spíše přesuneme oba XML soubory a máme po starostech…

Tímto krokem máme již vše připravené a můžeme se pustit do zkoušky programového připojení k databázi a získání výsledků nějakého dotazu. Ještě než se k tomu ale dostaneme, je nutné upozornit na jednu věc. Hibernate dává vývojářům k dispozici tři dotazovací nástroje, které je možné použít samostatně nebo v kombinaci:

  • vlastní dotazovací jazyk HQL, který je velmi podobný SQL – HQL
  • dotazy na základě definice různých omezovacích kritérií – Criteria Queries
  • standardní SLQ dotazy – Native SQL

My budeme po celou dobu zkušebního vývoje ukázkové aplikace prioritně používat HQL. Tím je všechno jasné a my můžeme do příslušné třídy ukázkové aplikace přidat novou proceduru, kde se pokusíme získat údaje z dotazu na tabulku:

private void hibernate_Query() {

            SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
            Session session = sessionFactory.openSession();

            Query query = session.createQuery("from udaje ");

            System.out.println(query.list().get(0).toString());

        session.flush();
        session.close();
    }

Toto je asi nejjednodušší možná varianta, která se skládá prakticky pouze ze tří příkazů pro připojení k databázi a zadání dotazu, jednoho s výpisem výsledků do konzole a dvou pro ukončení spojení s databází. První připojovací příkaz se odkazuje na konfigurační soubor (může zde být také zadán implicitně ve tvaru ..configure(„cesta ke XML souboru“)..), druhý otvírá příslušné sezení a třetí provádí dotaz. Jak je z příkazu patrné, v HQL není ani nutné (v případě, že chceme všechny záznamy z tabulky) uvádět jiné klíčové slovo než FROM (obdoba SQL příkazu SELECT * FROM udaje). Pro zobrazení výsledků použijeme vlastnost příkazu a z celkového balíku vybereme pouze první položku. Ukončení sezení by bylo možné ještě zjednodušit a vynechat první příkaz (příkaz session.flush se hodí spíše do jiných typů dotazů, protože vlastně „donutí“ aplikaci k synchronizaci mezi stavem databáze a mapovaných aplikačních objektů – viz např. zde Flush Mode. Je třeba také rozlišovat tento příkaz a použití databázových transakcí COMMIT/ROLLBACK).

Naopak by bylo možné (a někdy vhodné nebo dokonce nezbytné) kód „zkomplikovat“ a doplnit zachycení výjimek pomocí známé trojice příkazů try-catch-finally (jak je to většinou uváděno v různých návodech a příkladech). My ale ponecháme jednoduchou verzi a novou proceduru přidáme do akce tlačítka Zrušit změny, které opět nebude nijak jinak využívat. Výsledek spuštění je vidět na šestém obrázku druhé galerie. Na něm by bylo vhodné si všimnout tří věcí:

  • systém Hibernate ORM je docela „ukecaný“ a při běhu aplikace do konzole sype slušné množství informací
  • výsledek dotazu tam sice je, ale není zrovna moc „čitelný“…
  • v konzole je sice informace o ukončení aplikace (Aplikace byla řádně ukončena), ale chybí tam informace o ukončení procesu IJI (Process finished with exit code 0). Je také vidět dvě záložky s běžícím procesem IJI. Není to žádný zásadní problém, jenom je třeba očekávat, že třeba při zavírání IJI dojde k dotazům na ukončení běžících procesů. Pokud bychom i tuto maličkost chtěli vyřešit, stačí změnit příkazy pro ukončení sezení následovně:
    session.flush();
    sessionFactory.close();

Vzhledem k nečitelnosti zkusíme trochu jiný přístup, příkaz pro výpis do konzole zakomentujeme a přidáme dalších pár řádků kódu:

for (Iterator iterator = query.iterate(); iterator.hasNext();) {
            udaje data = (udaje) iterator.next();

            System.out.println(data.getId()+" , "+data.getCelecis()+" , "+data.getDescis()+" , "+data.getMaledes()+" , "+data.getRetezec()+" , "+data.getDatum());
        }

Zde využijeme možnosti iterace výsledků dotazu přes všechny záznamy a jednotlivé položky záznamu vypisujeme pomocí vytvořených Getterů. Výsledek je o dost lepší, jak to jasně ukazuje předposlední obrázek ve druhé galerii. Abychom si ještě ověřili možnost získání celé sady výsledků (jako to bylo v minulých dílech o JOOQ), tak přidáme ještě dva řádky:

ObservableList<udaje> list = javafx.collections.FXCollections.observableArrayList(query.list());
System.out.println(list.toString());

Poslední obrázek druhé galerie ale ukazuje, že výsledek opět není nic moc… Více si ale necháme na příští díl a ten dnešní ukončíme přílohou aktuální procedury: samexam6.java

V dnešním dílu jsme zahájili kapitolu o Hibernate ORM. Ukázali jsme si jednoduchou konfiguraci a vytvoření potřebných mapovacích nástrojů. Díl jsme zakončili jednoduchou ukázkou výpisu tabulkových dat do konzole. V příštím dílu na to navážeme a zaměříme se na zobrazení údajů v tabulce a také si ukážeme tři varianty mazání záznamů a dvě varianty aktualizace záznamů.