Programování v JavaFX: aktualizace a vkládání záznamů pomocí JOOQ, obecné možnosti

Jaromír Vojtaj 14. 1. 2016

Minulý díl byl věnován ukázce jednoduššího uložení výsledků dotazů. Tuto variantu jsme zobecnili tak, abychom si mohli zobrazit výsledky dotazu ve widgetu tabulky. Dnes ukončíme kapitolu o projektu JOOQ ukázkou aktualizace záznamů a vkládání nových záznamů do tabulky. Ukážeme si také některé obecné možnosti.

V minulém dílu jsme ukončili průvodce mazáním záznamů a v tom dnešním se podíváme na aktualizaci záznamů. Budeme přitom samozřejmě vycházet z podobných základů, jako v minulé ukázkové úloze. Jedinou změnou kvůli zjednodušení bude vypuštění všech kontrol zadávaných údajů v editačních polích. Proto můžeme proceduru pro inicializaci zkopírovat v podstatně kratším formátu (vypustíme i proceduru pro zrušení provedených změn, neb by byla úplně stejná jako minule…):

private void rec_Update() {
        tf_RW();
        u_Button.setDisable(false);
        e_Button.setDisable(false);
        u_Cele.requestFocus(); }

Samotnou výkonnou proceduru vytvoříme na základě jednoduchých návodů, které jsou v manuálu: Update Statement

Podobně jako tam si ukážeme dvě verze, které se liší použitým formátem příkazů a také verzi třetí, která přináší jednu zajímavou možnost pro aktualizaci záznamů. První verze bude velmi jednoduchá a přehledná:

private void save_Update() {
        DSLContext create = DSL.using(connDB("fxguide", "fxguide"), SQLDialect.POSTGRES);
        create.update(UDAJE)
                .set(UDAJE.CELECIS, Integer.valueOf(u_Cele.getText()))
                .set(UDAJE.DESCIS, BigDecimal.valueOf(Double.valueOf(u_Desetinne.getText())))
                .set(UDAJE.MALEDES, BigDecimal.valueOf(Double.valueOf(u_Maledes.getText())))
                .set(UDAJE.RETEZEC, u_Text.getText())
                .set(UDAJE.DATUM, java.sql.Date.valueOf(u_Datum.getText()))
                .where(UDAJE.ID.equal(pid.shortValue()))
                .execute();

        viewTable();
        tf_RO();
        a_Button.setDisable(true);
        e_Button.setDisable(true);
        u_Button.setDisable(true);
        tabPane.getSelectionModel().select(0); }

Jedná se o poměrně jednoduchou proceduru, takže provedeme jenom stručný komentář. První příkaz představuje obvyklé přihlášení do databáze. Pak následuje blok příkazů pro samotnou aktualizaci záznamů. Ten se odkazuje na příslušnou tabulku a pro jednotlivé aktualizované položky uvádí jejich názvy a nové hodnoty položek. Jak se asi dalo čekat, trochu problém je pole datum, kde bylo nutné provést jednoduchou konverzi. Předposlední příkaz v této sekci definuje záznam, kterého se změny budou dotýkat a poslední je výkonný příkaz. Pak už následuje pouze sekce s příkazy pro nové načtení tabulky a její zobrazení, nastavení editačních polí a tlačítek a nakonec přechod na první záložku formuláře s tabulkou. Novou proceduru můžeme přidat do akce příslušného tlačítka a vyzkoušet. Výsledek snažení je vidět na prvním obrázku v galerii. Ke druhé variantě uvedeme pouze začátek, protože poslední sekce bude samozřejmě úplně stejná:

private void save_Update2() {
        DSLContext create = DSL.using(connDB("fxguide", "fxguide"), SQLDialect.POSTGRES);
        create.update(UDAJE)
                .set(row(UDAJE.CELECIS, UDAJE.DESCIS, UDAJE.MALEDES, UDAJE.RETEZEC, UDAJE.DATUM),
                    row(Integer.valueOf(u_Cele.getText()), BigDecimal.valueOf(Double.valueOf(u_Desetinne.getText())), BigDecimal.valueOf(Double.valueOf(u_Maledes.getText())), u_Text.getText(), java.sql.Date.valueOf(u_Datum.getText())))
                .where(UDAJE.ID.equal(pid.shortValue()))
                .execute();

Jak je z výpisu patrné, uvádějí se názvy položek i jejich nové hodnoty v jednom příkazu. Aby byly procedura funkční, musíme přidat sami nebo za pomoci IJI potřebnou externí funkci:

import static org.jooq.impl.DSL.row;

Do akce tlačítka přidáme novou proceduru a druhý obrázek galerie nám ukazuje, že je aktualizace opět funkční. Proto se můžeme přesunout k poslední variantě, která se od těch předchozích liší. Uvedeme opět pouze část kódu bez závěrečné sekce:

private void save_Update3() {
        DSLContext create = DSL.using(connDB("fxguide", "fxguide"), SQLDialect.POSTGRES);
        Integer recCount = create.update(UDAJE)
                .set(row(UDAJE.CELECIS, UDAJE.DESCIS, UDAJE.MALEDES, UDAJE.RETEZEC, UDAJE.DATUM),
                        row(Integer.valueOf(u_Cele.getText()), BigDecimal.valueOf(Double.valueOf(u_Desetinne.getText())),
                                BigDecimal.valueOf(Double.valueOf(u_Maledes.getText())), u_Text.getText(), java.sql.Date.valueOf(u_Datum.getText())))
                .where(UDAJE.ID.equal(pid.shortValue()))
                .returning(UDAJE.CELECIS)
                .fetchOne().getValue(UDAJE.CELECIS);

        System.out.println(recCount.toString());

Jak je z kódu zřejmé, jsou provedené 4 změny:

  1. výkonný příkaz je přiřazen k proměnné celočíselného typu
  2. objevuje se nové klíčové slovo RETURNING. To označuje, kterou položku chceme získat. Dvě poznámky: zde se zadává pouze název položky + tato možnost platí pouze pro ty databáze (aktuálně Firebird a PG), které tuto funkčnost nativně podporují
  3. výkonný příkaz pro přiřazení nové hodnoty vybrané položky do deklarované proměnné
  4. výpis proměnné do konzole

Jak ukazuje třetí obrázek v galerii, je v konzole vidět skutečně aktuální hodnoty položky, kterou jsme vybrali (poslední záznam). Tím bychom mohli aktualizaci záznamů ukončit a přejít k poslední složce CRUD – vkládání nových záznamů. Ještě než se k tomu ale dostaneme, musíme jako posledně vyřešit problém s určením hodnoty klíčové položky v tabulce tak, aby byla vždy zaručeně unikátní. Minule jsme to řešili pomocí PG funkce. To bychom mohli sice také použít, ale nebylo by to úplně jednoduché – viz JOOQ + Stored Procedures

Z výše uvedených důvodů použijeme nástroje, které nám nabízí JOOQ a vytvoříme novou funkci:

private Integer maxKFV() {          //1
        DSLContext create = DSL.using(connDB("fxguide", "fxguide"), SQLDialect.POSTGRES);
        Short maxID = (Short) create    //2
                .select(UDAJE.ID.max().add((short) 1))  //3
                .from(UDAJE)        //4
                .fetchOne()     //5
                .getValue(0);       //6
        return maxID.intValue();    //7 }

Vše se zdá být celkem jednoduché, ale přesto přidáme stručný komentář:

  1. řádek – funkce deklarujeme jako celočíselnou. Bylo by asi vhodnější to udělat jinak, ale je to tak uděláno vzhledem k typu příslušné proměnné, kterou máme v proceduře deklarovanou
  2. řádek – lokální proměnná musí mít stejný typ, jako má příslušná položka v tabulce. Výkonný příkaz tak musíme také deklarovat v příslušném typu
  3. řádek – zde využíváme možností JOOQ, kde jsou k dispozici prakticky všechny běžné agregační funkce a nejen to! Rovnou zde zvyšujeme maximální hodnotu klíčové položky
  4. řádek – definice tabulky je jasná
  5. řádek – použijeme jiný typ příkazu. Tento se používá tehdy, když chceme mít ve výsledku pouze jeden záznam
  6. řádek – definujeme položku ze seznamu. Zde je pouze jedna, takže máme jenom jednu možnost
  7. řádek – výsledek převádíme na příslušný typ návratové hodnoty

Funkci na zajištění nové unikátní hodnoty klíčové položky máme k dispozici a z minulé ukázkové úlohy si zkopírujeme do té aktuální procedury setFocus a new_Record. V té druhé uvedené pouze změníme způsob volání předchozí funkce maxKFV do aktuálního formátu. Tím máme vše připravené a můžeme se pustit do vlastní procedury pro uložení nového záznamu do tabulky. Opět vynecháme kontrolu editačních polí a vyjdeme z manuálových stránek: Insert Statement.

private void save_Record() {
        DSLContext create = DSL.using(connDB("fxguide", "fxguide"), SQLDialect.POSTGRES);
            create
                    .insertInto(UDAJE,
                            UDAJE.ID,UDAJE.CELECIS,UDAJE.DESCIS,UDAJE.MALEDES,UDAJE.RETEZEC,UDAJE.DATUM)
                    .values(
                            pid.shortValue(),
                            Integer.valueOf(n_Cele.getText()),
                            BigDecimal.valueOf(Double.valueOf(n_Desetinne.getText())),
                            BigDecimal.valueOf(Double.valueOf(n_Maledes.getText())),
                            n_Text.getText(),
                            java.sql.Date.valueOf(dp_Datum.getValue()))
                    .execute();

        viewTable();
        sr_Button.setDisable(true);
        n_ID.setText("");
        tabPane.getSelectionModel().select(0); }

Procedura je velmi podobná ukládání aktualizovaných záznamů, což je celkem logické. Je také velmi podobná klasickému SQL příkazu, takže upozorníme pouze na odlišný způsob uložení položky datum pomocí widgetu DatePicker. Proceduru přiřadíme k akci příslušného tlačítka a ve dvou krocích ji můžeme vyzkoušet. První krok inicializace nového záznamu (viz čtvrtý obrázek galerie) ukazuje na správnou funkčnost vytvoření nového unikátního záznamu. Než přistoupíme ke druhému kroku, což bude samotné uložení nového záznamu do tabulky, tak ještě do procedury initialize zkopírujeme kód handleru pro změny v DatePicker. Pak už se můžeme na pátém obrázku v galerii přesvědčit, že vložení nového záznamu opravdu funguje. Tím ale ještě nekončíme, protože si ukážeme druhou variantu, která je podobná poslední možnosti při ukládání aktualizovaných záznamů. Uvedeme si pouze část kódu nové procedury:

Result<?> res = create...

Výkonný příkaz deklarujeme jako zvláštní typ. Je to podobné jako výše, ale nedefinujeme pouze jednu návratovou proměnnou, ale celou jejich sadu. To se více objasní v dalším kódu:

//execute();
.returning(UDAJE.ID,UDAJE.CELECIS,UDAJE.DESCIS,UDAJE.MALEDES,UDAJE.RETEZEC,UDAJE.DATUM)
.fetch();

Zde místo jednoho prováděcího příkazu přidáme dva další. První definuje rozsah či obsah deklarované proměnné a určuje, které položky v ní budou k dispozici. Druhý je pak prováděcím příkazem, který do proměnné uloží kompletní záznam, který byl do tabulky právě vložen. Posledním příkazem je pak zobrazení tohoto záznamu v konzole:

System.out.println(res.toString());

Výsledek nového kódu je vidět na šestém obrázku galerie. Tím bychom ukončili ukázky CRUD v podání projektu JOOQ. Ještě ale úplně neukončíme přísun informací o samotném JOOQ a přidáme dvě, která by se případným zájemcům mohly hodit. První z nich se týká samotného úvodu, kdy jsme generovali aplikační procedury. Nyní si ukážeme, co by se stalo, pokud bychom udělali v kódu příslušné generující procedury velmi „drobnou“ změnu:

.withIncludes(".*")

původně jsme měli toto:

.withIncludes("udaje.*")

Původní nastavení parametrů způsobilo, že se vytvořily aplikační procedury pouze pro jednou vybranou tabulku. Obecné nastavení je takové, které do generování zahrne všechny položky z vybraného schématu. Jak vypadá struktura generovaných procedur si můžeme podrobně prohlédnout na předposledním obrázku v galerii. Z něj je zřejmé, že jsou zde obsažené nejenom všechny tři tabulky, ale také pohledy (které můžeme v případě potřeby samozřejmě využít úplně stejně, jako tabulky) a hlavně PG funkce. Ty se ukrývají pod názvem Routines. Tím jenom doplňujeme předchozí informaci o možnosti využití uložených procedur v JOOQ aplikacích.

Poslední věc, kterou si dnes ukážeme, je pár možností, které nám projekt JOOQ dává v oblasti zobecnění různých příkazů a procedur. My jsme si již něco ukázali, a tak to doplníme. Pro zajištění možnosti zobecnit procedury a funkce pomocí parametrů potřebujeme kromě jiného velmi podrobně znát kompletní schéma používané databáze. V kontextu schématem myslíme hlavně názvy tabulek, názvy polí v tabulkách a jejich datový typ. K získání takových informací je samozřejmě možné použít obecné SQL dotazy nebo jim podobné dotazy, které umožní vytvořit i JOOQ (nebylo součástí našeho výkladu). Vždy je ale třeba vycházet z možností, které nám dávají jednotlivé typy databázových strojů. Proto si zde ukážeme obecnou možnost, kterou nám na základě vygenerovaných aplikačních procedur dává samotné JOOQ. Za tímto účelem si vytvoříme novou proceduru a její volání přidáme k tlačítku pro zrušení změn, které v aktuální úloze nepoužíváme:

private void pokus() {

        Row row = UDAJE.getSchema().getTable("udaje").fieldsRow();

        System.out.println(UDAJE.getSchema().getName());
        System.out.println(UDAJE.getSchema().getTables());
        System.out.println(UDAJE.getSchema().getTable("udaje").fieldsRow());

        for (int i=0;i<row.size();i++)
            System.out.println(row.dataType(i));
        }

Poslední obrázek galerie nám pak postupně ukazuje název schématu databáze PG, název tabulky, název položek ve vybrané tabulce a datové typy jednotlivých položek v tabulce. Všechny názvy jsou samozřejmě takové, které jsou k dispozici přímo v objektech PG, nikoliv v generovaných aplikačních procedurách. Tomto bychom kapitolku o JOOQ mohli ukončit a do přílohy si dáme finální podobu příslušné procedury ukázkové úlohy: samexam5.java.

V dnešním dílu jsme se věnovali 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 směrem k zobecnění databázových funkcí a procedur. Tím jsme vlastně ukončili kapitolu o JOOQ. V příštím dílu zahájíme kapitolu o ORM Hibernate a ukážeme si jeho konfiguraci a jednoduché zobrazení dat z tabulky do konzole.

Ohodnoťte jako ve škole:

Průměrná známka 5,00

Našli jste v článku chybu?
Zasílat nově přidané názory e-mailem
Podnikatel.cz: Co všechno ví Heureka o e-shopech?

Co všechno ví Heureka o e-shopech?

Vitalia.cz: Jsou káva a horké nápoje karcinogenní?

Jsou káva a horké nápoje karcinogenní?

120na80.cz: Odřenina. Jakou použít dezinfekci?

Odřenina. Jakou použít dezinfekci?

120na80.cz: Jak správně vytrhnout mléčný zub?

Jak správně vytrhnout mléčný zub?

Měšec.cz: Udali ho na nelegální software a přišla Policie

Udali ho na nelegální software a přišla Policie

Vitalia.cz: Vydával se za český, prozradila ho DNA

Vydával se za český, prozradila ho DNA

Podnikatel.cz: Když už je sexy, tak ať taky funguje

Když už je sexy, tak ať taky funguje

Vitalia.cz: Tetanus v USA – i po odřeninách

Tetanus v USA – i po odřeninách

Lupa.cz: Válka e-shopů. Alza končí s Heurekou

Válka e-shopů. Alza končí s Heurekou

Vitalia.cz: Falšované masné výrobky byly v Tescu i Lidlu

Falšované masné výrobky byly v Tescu i Lidlu

DigiZone.cz: Kolik lidí sleduje hokej na webu ČT?

Kolik lidí sleduje hokej na webu ČT?

120na80.cz: Jak si udržet zdravou vaginu

Jak si udržet zdravou vaginu

DigiZone.cz: Změní se veřejnoprávní status ČT?

Změní se veřejnoprávní status ČT?

120na80.cz: Zjistěte, zda je vaše klíště infikované

Zjistěte, zda je vaše klíště infikované

DigiZone.cz: Šlágr TV: pokuta 100 tisíc za on-line

Šlágr TV: pokuta 100 tisíc za on-line

DigiZone.cz: Šlágr TV dostala pokutu 100 000 Kč

Šlágr TV dostala pokutu 100 000 Kč

DigiZone.cz: Panasonic v Praze uvedl TV pro rok 2016

Panasonic v Praze uvedl TV pro rok 2016

Podnikatel.cz: Různé podoby lahve Coca–Coly. Úchvatné

Různé podoby lahve Coca–Coly. Úchvatné

Podnikatel.cz: Vyzkoušejte k propagaci výrobku Microsites

Vyzkoušejte k propagaci výrobku Microsites

Podnikatel.cz: Etický kodex firmy nezachrání

Etický kodex firmy nezachrání