Moje odkazy
Obsah článku:
vydáno: 27. 1. 2010 05:47, aktualizováno: 22. 9. 2014 16:53
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.
Opět si stáhneme aktuální verzi zdrojových kódů k dnešnímu dílu seriálu:
$ hg pull
$ hg up "3. díl"
Případně si je můžete stáhnout jako bzip2 archiv přes web.
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ů:
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á.
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 fungvat 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 jako jméno, heslo, port a databázový server.
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
.
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í.
JdbcTemplate
.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.
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)
PRIMARY KEY,
nazev
CHARACTER VARYING(255)
NOT NULL,
datum
TIMESTAMP WITH TIME ZONE
DEFAULT now()
);
-- vlastník schématu:
ALTER TABLE podnik OWNER TO nekurak;
GRANT ALL ON TABLE podnik TO nekurak;
-- uživatel, kterého používá webová aplikace:
GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE podnik TO nekurak_web;
Při tvorbě bezpěč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.
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>
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 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ě.
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);
}
}
(tip pro Javu 7 a novější: blok try + AutoCloseable)
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.
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 :-).
Tento článek zatím nikdo nekomentoval