Přejít k navigační liště

Zdroják » Různé » Java na webovém serveru: práce s databází

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

Články Různé

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.

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

Komentáře

Subscribe
Upozornit na
guest
7 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Martin

projel jsem v rychlosti cely clanek a vypada moc hezky. Clanek je spise pro pokrocilejsi uzivatele javy nez pro zacatecniky (kdo z novacku pouziva glassfish? vsichni sahnou po tomcatu nebo jetty).

Ale nerozumim, jak muzete v ukazkach zdrojovych kodu kombinovat cestinu a anglicitnu v nazvu metod…viz getSpojeni(), getPodniky() apod. Vzdyt to je prece sileny paskvil a takove pouzivani cestiny ve zdrojacich je uvadeno jako odstrasujici priklad v kazde druhe publikaci o programovani.

Jinak hodnotim clanek 1– (jako ve skole)… :-)

bender

„Clanek je spise pro pokrocilejsi uzivatele javy nez pro zacatecniky (kdo z novacku pouziva glassfish?“

IMHO clanku typu, „zaciname v JAVE“ je vsude plno, bohuzel tam to take casto konci a malokdy na stejnych strankach narazite na pokrocilejsi problematiku…

O tom po cem novacek sahne by se dalo asi polemizovat. Glassfish je hodne znamy pojem imo vice nez jetty.
No offence, jen ma pripominka.

krespo

celkom pekna zacina tato seria clankov :).Ak by bolo mozne bolo by tu fajn spravit clanok o autentifika­cii,autorizacii najlepsie s jaas. Dost vela zaciatocnikov sa pyta na temu overenia pristupu atd..

Mir a B.

jdbc ovladac (jar) je nutne nainstalovat do ${HOME}/netbeans/6.8/GlassFish_v4/lib/ext, ne do /usr/local/sges-v3/glassfish/domains/domain1/lib/ext.

Potom je nutne glassfish restartovat (v NetBeans, zalozka Services).

Jinak se muze pri pingu zobrazit chyba tykajici se classpath  org.postgresql.ds.PGConnectionPoolDataSource

Tonda

Já sem to samé řešil na windows. Nefungoval ping… nakonec zafungovalo dát ovladač do glassfish/lib místo glassfish/doma­ins/domain1/lib/ex­t. Třeba to někomu ušetří trochu času :)

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.

Pocta C64

Za prvopočátek své programátorské kariéry vděčím počítači Commodore 64. Tehdy jsem genialitu návrhu nemohl docenit. Dnes dokážu lehce nahlédnout pod pokličku. Chtěl bych se o to s vámi podělit a vzdát mu hold.