Java na webovém serveru: práce s databází

Prakticky každá aplikace potřebuje někam ukládat a odněkud načítat data. K tomu se obvykle používají databáze. Dnes se podíváme na to, jak se z naší aplikace připojit k relační databázi a provádět základní operace.

Seriál: Java na webovém serveru (16 dílů)

  1. Java na serveru: úvod 8.1.2010
  2. Java na webovém serveru: první web 15.1.2010
  3. Java na webovém serveru: práce s databází 29.1.2010
  4. Java na webovém serveru: práce s databází II 12.2.2010
  5. Java na webovém serveru: lokalizace a formátování 19.2.2010
  6. Java na webovém serveru: autorizace a autentizace 26.2.2010
  7. Java na webovém serveru: autorizace a autentizace II 5.3.2010
  8. Java na webovém serveru: porovnání Javy a PHP 10.3.2010
  9. Java na webovém serveru: Vlastní JSP značky a servlety 17.3.2010
  10. Java na webovém serveru: posílání e-mailů a CAPTCHA 24.3.2010
  11. Java na webovém serveru: píšeme REST API 7.4.2010
  12. Java na webovém serveru: SOAP webové služby 14.4.2010
  13. Java na webovém serveru: hlasování a grafy v SVG 28.4.2010
  14. Java na webovém serveru: Komentáře a integrace s Texy 9.6.2010
  15. Java na webovém serveru: AJAX formuláře 23.6.2010
  16. Java na webovém serveru: implementujeme Jabber 30.6.2010

Dnešní pokračování opět zahájíme tím, že si stáhneme aktuální verzi zdrojových kódů k aktuálnímu dílu seriálu, ideálně pomocí Mercurialu:

$ hg pull
$ hg up "3. díl"

Případně si je můžete stáhnout jako bzip2 archiv přes web.

Spojení spravované kontejnerem

Z jiných jazyků jste možná zvyklí vytvářet si databázová spojení přímo v aplikaci – zadat jméno, heslo, IP adresu serveru, připojit…. Tenhle přístup můžete samozřejmě praktikovat i v Javě, ale zajímavější je nechat tuhle nudnou práci na aplikačním serveru (kontejneru). To je výhodné zejména ze dvou důvodů:

  • Connection pooling
  • Minimální nebo nulová konfigurace aplikace

Navázat databázové spojení vždy představuje nějakou režii – kromě navázání TCP spojení je třeba ověřit jméno a heslo a přidělit uživateli určité prostředky na straně databáze – to vše stojí čas a provádět tyto kroky při každém načtení stránky zbytečně zdržuje. Proto se používá tzv. Connection pooling – vytvoří se „bazének“, ve kterém databázová spojení čekají, až je bude někdo (aplikace) potřebovat. Místo vytváření spojení si ho aplikace vyžádá z poolu a když už ho nepotřebuje, spojení uzavře – nedojde však ke skutečnému ukončení spojení s databází, ale pouze k jeho návratu do „bazénku“. Spojení se zde recyklují, ale z pohledu aplikace je tento proces transparentní.

Nastavení databázových spojení i parametrů poolu (velikost atd.) děláme na úrovni aplikačního serveru a v aplikaci se o ně už nemusíme starat. Na tom je příjemné, že aplikaci můžeme jednou zkompilovat (jako .war) a podle toho, zda ji nasadíme např. na testovací nebo na provozní server, se bude připojovat k příslušné databázi – není potřeba ji zvlášť ručně konfigurovat. Jednou definovaný databázový pool může používat zároveň několik aplikací.

Asi vás zajímá, jak aplikace „bazének“ najde, aby si z něj mohla vyžádat spojení. K tomu slouží tzv. JNDI, což je jmenná služba používaná v Javě. Velmi zjednodušeně řečeno, v aplikačním serveru definujeme JNDI jméno (např. jdbc/nekurak) pro náš pool a aplikace ho podle tohoto jména dohledá.

Nastavení Glassfishe

Aplikační server sám od sebe s databázemi jako je PostgreSQL nebo MySQL pracovat neumí – musíme si nejprve nainstalovat JDBC ovladač, který si stáhneme ze stránek našeho DBMS. Je to trochu práce, ale na druhou stranu není problém připojit se k libovolné databázi – JDBC ovladače totiž existují prakticky pro cokoli. Soubor .jar s JDBC ovladačem (např. postgresql-8.4-701.jdbc4.jar) umístíme do složky glassfish/domains/domain1/lib/ext v instalaci našeho Glassfishe a restartujeme ho. Naše aplikace používá PostgreSQL, ale pokud máte oblíbenou jinou databázi (MySQL…), stačí, když si vytvoříte stejné tabulky v ní – minimálně dnešní příklad vám fungovat bude.

Přes webové rozhraní si vytvoříme nejprve „Connection Pool“ – jako „Resource Type“ zvolíme javax.sql.ConnectionPoolDataSource a zadáme potřebné údaje: jméno, heslo, port a databázový server.

Java na webovém serveru 3 - Pool

Pomocí tlačítka Ping si můžeme vyzkoušet, že jsme „Connection Pool“ vytvořili správně. Následně v „JDBC Resources“ přiřadíme vytvořenému poolu JNDI jméno  jdbc/nekurak.

Způsoby práce s databází

Jak už to ve světě Javy bývá, k jednomu cíli vede několik cest. Je tedy několik způsobů, jak v aplikaci pracovat s databází.

  • JSP značky – v rámci standardní knihovny máme k dispozici sadu značek pro práci s SQL. Stačí si importovat příslušný jmenný prostor a můžeme přistupovat k databázi přímo z JSP stránky.
  • Vlastní JDBC spojení – Vytvoříme si DAO vrstvu (sadu tříd) a v ní budeme pracovat s JDBC spojením.
  • Cizí framework – pokud nám v předchozím bodě připadalo, že vymýšlíme znovu kolo, můžeme použít nějaký hotový framwork jako je třeba Spring a jeho  JdbcTemplate.
  • ORM – ještě vyšší úrovní abstrakce je objektově-relační mapování – např. Hibernate nebo jiná implementace JPA. V takovém případě přestáváme používat SQL a pracujeme s jazykem JPQL.

Ještě je tu jedna možnost – obejít služby nabízené aplikačním serverem a režírovat si spojení k databázi v aplikaci. Tento způsob je sice možný, ale připravíme se tím o výše zmiňované výhody. Můžeme ho tedy zavrhnout – ale zbylé čtyři způsoby jsou legitimní a všechny mají své výhody a nevýhody – vždy je potřeba zvážit konkrétní podmínky a potřeby aplikace.

Nejméně často využijeme SQL JSP značky – hodí se leda na velmi jednoduché nebo jednoúčelové aplikace – hlavní nevýhodou je nemožnost vložit aplikační logiku – aplikační vrstva úplně chybí a data putují rovnou z databáze do prezentační vrstvy. Na druhou stranu je jejich použití nejjednodušší a nejméně pracné. Dnes se podíváme na první dvě možnosti přístupu k databázi.

Naše databáze

V dnešním dílu si vystačíme s velice jednoduchou databází – obsahuje jedinou tabulku se třemi sloupečky.

CREATE TABLE podnik
(
  id integer NOT NULL DEFAULT nextval('podnik_seq'::regclass),
  nazev character varying(255) NOT NULL,
  datum timestamp with time zone DEFAULT now(),
  CONSTRAINT podnik_pk PRIMARY KEY (id)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE podnik OWNER TO nekurak;
GRANT ALL ON TABLE podnik TO nekurak;
GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE podnik TO nekurak_web;

Pár slov k bezpečnosti

Při tvorbě bezpečných aplikací byste měli dodržovat  koncept minimálních práv. To znamená, že subjekt by měl mít jen taková oprávnění, jaká skutečně potřebuje pro svoji činnost – žádná další. Příklad: Potřebuje vaše aplikace mazat záznamy z tabulky logů? Ne? Proč tedy má právo na DELETE? Potřebuje aplikace někde zobrazovat hesla uživatelů? Ne? Proč má tedy právo na SELECT? Heslo se dá ověřit pomocí uložené procedury či funkce – jejím parametrem bude jméno a heslo a návratová hodnota bude true nebo false.

Jakkoli vám tento přístup může připadat paranoidní, je dobré se jím řídit. I ta nejlepší aplikace může obsahovat chyby a je žádoucí minimalizovat ztráty pro případ, že by útočník získal přístup k databázovému spojení – pokud se nedostane k citlivým údajům (např. hesla uživatelů) nebo nebude moci smazat důležité záznamy logů, jsou to kladné body pro vás a zmírňují následky bezpečnostního incidentu. Je proto vhodné, aby vlastníkem databáze/schématu byl jiný uživatel než ten, pod kterým se připojuje aplikace – ten má přidělená jen ta práva, která potřebuje.

JSP značky

V tomto případě nemusíme vytvářet žádné třídy v Javě a s SQL databází pracujeme rovnou v JSP stránce. Potřebná knihovna značek má jmenný prostor http://java.sun.com/jsp/jstl/sql.

Položíme jednoduchý SQL dotaz a výsledek si načteme do proměnné podniky (jako objekt implementující rozhraní  javax.servlet.jsp.jstl.sql.Result).

<sql:query var="podniky">SELECT * FROM podnik;</sql:query>

Výsledek následně vypíšeme jako seznam:

<c:forEach items="${podniky.rowsByIndex}" var="p">
    <li><c:out value="${p[1]}"/></li>
</c:forEach>

V dotazech můžeme používat parametry:

<sql:query var="podniky" sql="SELECT * FROM podnik WHERE nazev = ?;">
    <sql:param value="Na Kovárně"/>
</sql:query>

Celý příklad najdete v souboru sql-znacky.jsp. Pomocí JSP značek můžeme provádět i DML operace (viz <sql:update/>) nebo používat transakce (zabalením bloku do <sql:transaction/>). Moc kouzel se s tím ale dělat nedá.

Jak je vidět, největší výhoda tohoto přístupu – jednoduchost – je i jeho velkou nevýhodou. Přicházíme o výhody vrstvené architektury a velmi brzy nám přestane stačit – značky pro práci s databází tak v reálné aplikaci asi nevyužijete, ale v nějakém prototypu by se vám mohly hodit.

Poznámka: při používání SQL JSP značek je potřeba v souboru web.xml uvést zdroj, se kterým budeme pracovat:

<resource-ref>
    <res-ref-name>jdbc/nekurak</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
</resource-ref>

Vlastní JDBC spojení

Vraťme se tedy k původnímu konceptu, kdy JSP stránka se stará jen o prezentaci a data jí poskytuje beana (javová třída PodnikyWeb). Vytvoříme si datovou vrstvu, která bude prozatím tvořena jedinou třídou PodnikDAO. V DAO vrstvě soustředíme veškerý kód týkající se práce s databází a nedovolíme mu, aby se rozlézal do zbytku aplikace – ta by měla abstrahovat od skutečnosti, že se v DAO vrstvě pracuje s relační databází – stejně tak by data mohla pocházet z nějaké webové služby nebo třeba ze souborového systému.

SQL v samostatných souborech

SQL příkazy můžeme zapisovat rovnou do kódu v Javě (např. jako konstanty), nicméně za vhodnější považuji vyčlenění všeho SQL kódu do samostatných XML souborů. Výhodou je, že drobné úpravy SQL dotazů nebo jinou parametrizaci může provádět i neprogramátor (např. správce serveru) a není potřeba aplikaci kompilovat. Také máte přehled, jaké SQL vaše aplikace obsahuje – všechno SQL je na jednom místě a ne rozprostřené mezi ostatním javovým kódem. O načítání hodnot z XML souborů se stará třída SuperDAO (kterou můžete použít i jako samostatnou knihovnu – zdrojáky).

XML soubory jsou pojmenované stejně jako příslušná třída, takže např. PodnikDAO.java bude mít svoje SQL dotazy v souboru PodnikDAO.sql.xml a případné další vlastnosti v PodnikDAO.xml. XML soubory mají tuto strukturu:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <entry key="SELECT_VSECHNY">
    <![CDATA[
    SELECT * FROM podnik;
    ]]>
    </entry>
</properties>

Tímto způsobem můžeme zapsat i složité několikařádkové SQL dotazy – přehledněji než ve zdrojáku v Javě. Zápis s <![CDATA[ ]]> se hodí pro případ, že SQL dotaz obsahuje např. porovnání (ostré závorky). Pokud vám tento přístup nevyhovuje, nebo se vám zdá pro vaši aplikaci předimenzovaný, nic vám nebrání zapisovat SQL přímo do kódu v Javě.

Rozdíl oproti JavaSE

S databází zde pracujeme tak, jak jsme zvyklí ze standardní platformy, jediným rozdílem je, že JDBC spojení nevytváříme ručně v aplikaci, ale získáváme ho přes javax.naming.InitialContext a JNDI jméno – viz metoda getSpojeni() třídy  NekurakSuperDAO.

Metoda pro načtení všech podniků z databáze vypadá takto:

public Collection<Podnik> getPodniky() {
    Connection db = getSpojeni();
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        ps = db.prepareStatement(getSQL(SQL.SELECT_VSECHNY));
        rs = ps.executeQuery();
        Collection<Podnik> vysledek = new ArrayList<Podnik>();

        while (rs.next()) {
        vysledek.add(new Podnik(rs.getInt("id"), rs.getString("nazev")));
        }

        return vysledek;
    } catch (Exception e) {
        log.log(Level.SEVERE, "Chyba při získávání podniků.", e);
        return null;
    } finally {
        zavri(db, ps, rs);
    }
}

Metoda pro ukládání ulozPodnik(Podnik p) je tvořena obdobně (viz třída  PodnikDAO).

Jak je vidět, i prosté načtení záznamů z databáze s sebou nese poměrně dost nudného kódu (odchytávání výjimek, mapování relací na objekty…). Na druhou stranu nás to nemusí tolik trápit, protože všechen tento nezáživný kód je vyčleněn do datové vrstvy, zatímco ve vyšších vrstvách aplikace můžeme mít čistou a přehlednou obchodní logiku.

Poznámka: návrh datové vrstvy bývá i složitější – můžeme oddělit implementaci DAO a rozhraní a použít návrhový vzor továrna… nicméně se obávám, že robustnější design by byl nad rámec potřeb prosté webové aplikace – někdy je totiž lepší dělat věci jednodušeji, zvlášť když můžeme kód refaktorovat a v případě potřeby přidat další vrstvy.

Závěr

V dnešním díle jsme se naučili vytvářet v aplikačním serveru pool databázových spojení a vysvětlili si jeho fungování. Ukázali jsme si ten nejjednodušší způsob práce s databází (JSP značky) a ten o trošku složitější (DAO vrstvu). Příště se podíváme na zbylé dva způsoby přístupu k databázi, které nám ušetří dost práce, ale dost nám jí i přidělají. A hlavně nezapomínejte, že psaní softwaru by měla být zábava.

Odkazy

Franta Kučera působí jako Java vývojář na volné noze. Programování je jeho koníčkem už od dětství. Kromě toho má rád Linux, relační SŘBD a XML.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 7

Přehled komentářů

Martin moc pekne....ale
František Kučera Čeština
bender Re: moc pekne....ale
krespo inspiracia
Mir a B. Postgresql ping nefungoval
František Kučera Re: Postgresql ping nefungoval
Tonda Re: Postgresql ping nefungoval
Zdroj: https://www.zdrojak.cz/?p=3163