Spring JdbcTemplate?
Úno/10 2
Když vytváříte datovou vrstvu své aplikace, narážíte někdy na nutnost psát hodně nudného a opakujícího se kódu – získat spojení z databáze, nastavit parametry dotazu, odchytávat výjimky, uzavírat spojení… Proto existují různé pomůcky, které nám práci usnadní. Jednou z nich je JdbcTemplate
z frameworku Spring. Dnes se podíváme na to, s čím nám pomůže a od čeho nás naopak nezachrání – prostě trochu střízlivější pohled, než najdete ve většině tutoriálů.
Jako ukázkový příklad jsem zvolil velice jednoduchou webovou „aplikaci“, která vypíše na stránku seznam knížek. Jedná se o ukázku v podobném rozsahu jako můj starší příklad: Ukázka ORM – Hibernate JPA.
Klasické DAO
Ručně psané DAO (zjednodušené, bez rozhraní) vypadá nějak takto:
public Collection<Kniha> getKnihy() { Collection<Kniha> vysledek = new ArrayList<Kniha>(); Connection db = null; PreparedStatement ps = null; ResultSet rs = null; try { db = dataSource.getConnection(); ps = db.prepareStatement("SELECT * FROM sbirka.kniha;"); rs = ps.executeQuery(); while (rs.next()) { Kniha k = new Kniha(); k.setNazev(rs.getString("nazev")); k.setAutor(rs.getInt("autor")); k.setDatumAktualizace(rs.getDate("datum")); k.setId(rs.getInt("id")); k.setIsbn(rs.getString("isbn")); k.setPocetStran(rs.getInt("pocet_stran")); k.setRokVydani(rs.getInt("rok_vydani")); vysledek.add(k); } } catch (Exception e) { log.log(Level.SEVERE, "Chyba načítání knížek.", e); return null; } finally { zavri(db, ps, rs); } return vysledek; }
Více viz třída KnihaDAOjdbc
. Spousta řádků kódu a přitom chceme jen takovou blbost – načíst jeden objekt (resp. jejich seznam) z databáze.
DAO vrstva pomocí JdbcTemplate
Takže si teď ukážeme, jak tuhle metodu zjednodušit pomocí springového JdbcTemplate
.
public Collection<Kniha> getKnihy() { return t.query("SELECT * FROM sbirka.kniha;", knihaRowMapper); }
Skvělé ne? Ze spousty řádků kódu máme najednou jen jeden. A dělá to to samé – načítá záznamy z databáze :-) Pojďme se teď podívat, jak tenhle zázrak funguje.
Mapování pomocí RowMapperu
Asi vás napadlo, že to není jen tak samo sebou – někde se přece musí stanovit, které sloupečky v databázi odpovídají kterým proměnným/metodám v javových třídách.
Proto jsme si museli vytvořit třídu KnihaRowMapper
, která implementuje rozhraní ParameterizedRowMapper
a má metodu mapRow
:
public Kniha mapRow(ResultSet rs, int i) throws SQLException { Kniha k = new Kniha(); k.setNazev(rs.getString("nazev")); k.setAutor(rs.getInt("autor")); k.setDatumAktualizace(rs.getDate("datum")); k.setId(rs.getInt("id")); k.setIsbn(rs.getString("isbn")); k.setPocetStran(rs.getInt("pocet_stran")); k.setRokVydani(rs.getInt("rok_vydani")); return k; }
Tato metoda se zavolá na každém řádku SQL výsledkové sady a vrátí příslušný objekt (knížku). O zbytek se už postará JdbcTemplate
.
K opačnému procesu, tedy nastavení parametrů z objektu do INSERT/UPDATE příkazu slouží KnihaPSSetter
implementující PreparedStatementSetter
(viz zdrojáky).
Jak tedy vidíme, nic není zadarmo, žádný kód nezmizel, nedošlo k zázračnému smrsknutí z dvaceti řádků na jeden. Jen se dané řádky přesunuly do samostatné třídy. To je ale dobře – vytvořili jsme znovupoužitelný kus kódu a budeme z něj časem čerpat výhody. Třeba až budeme chtít přidat další metodu, která nebude načítat všechny (kolekci) záznamy, ale třeba jen jeden konkrétní, nebo nějakou podmnožinu. Už nebudeme muset podruhé psát (nebo trapně kopírovat) kód, který říká, který sloupeček odpovídá kterým vlastnostem objektu.
Nekontrolované výjimky jsou zlo
Jistě jste si všimli i toho, že varianta využívající JdbcTemplate
nijak neošetřuje výjimky. Je to tím, že Spring převádí kontrolované SQL výjimky na běhové (nekontrolované). Běhové výjimky nás kompilátor nenutí odchytávat a řešit – a proto najednou kód vypadá úžasně jednoduše.
Praxe je ale krutější – to, že běhovou výjimku nemusíme odchytávat, neznamená, že chyba nemůže nastat. Naopak, pravděpodobnost, že dojde k výjimce (např. překlep v názvu tabulky) je naprosto stejná, ať Spring používáme nebo ne. Nekontrolované výjimky tak vlastně jen vytvářejí iluzi toho, že je vše v pořádku. Stejného efektu ale můžeme dosáhnout tím, že budeme kontrolované výjimky vyhazovat (throws
) z metod výše a výše.
Jenže v případě kontrolovaných výjimek alespoň víme, na čem jsme – víme, jaké chyby v daném kousku kódu nastávají a kompilátor nás nutí se s nimi nějak vyrovnat. V případě jejich nekontrolovaných kolegyň vyletí výjimka až tak vysoko, kam ji pustíme (museli bychom ji dobrovolně a disciplinovaně někde odchytávat). V poslední instanci zasáhne až aplikační server a v případě webové aplikace zobrazí uživateli standardní 500 HTTP chybovou stránku.
Samozřejmě nejsme lamy a píšeme si vlastní chybové stránky, takže na uživatele nevyskočí ta standardní s SQL výjimkou a dalšími podrobnosti, ale jen nějaká strohá omluva, že aplikace bohužel nefunguje, přijďte jindy. Takže jsme situaci jakoby vyřešili, ale…
Robustnost aplikace
Dovolte mi ocitovat část textu z knížky Dokonalý kód od Steva McConnella – kapitola 8.3 Techniky ošetřování chyb:
Určité programy ukončí svůj běh při zjištění každičké chyby. Tento přístup je vhodný zejména u aplikací s velkým důrazem na zabezpečení. Pokud software řídící nemocniční rentgen načte nesprávná data týkající se dávkování radiace, jaká bude asi nejlepší možná reakce? … V takovém případě je rozhodně jednoznačně nejlepší vypnout přístroj. Raději přístroj restartujeme, než abychom riskovali, že pacienta ohrozíme přílišnou radiací. … Jak nám ukázaly příklady videohry či rentgenu, závisí styl ošetření chyby na typu softwaru, v němž k chybě dojde.
A dále upozorňuje na protiklad mezi bezchybností (raději nevrátit žádný výsledek, než vrátit nepřesný) a robustností (vždy se snažit udržet systém v chodu, i když to může vést k občas nepřesným/nedokonalým výsledkům).
Mějme webovou aplikaci (e-obchod) a v ní komponentu, která zobrazuje, kdo má dnes svátek (z nějakého důvodu je móda cpát tuhle informaci na každý web).
Představme si, že tahle komponenta selže (např. nějaký líný administrátor zapomněl nastavit práva na SELECT této tabulky). Pokud budeme bezstarostně používat běhové výjimky a necháme chybu vyletět až úplně nahoru, uživateli se zobrazí chybová stránka – aplikace bude mimo provoz a zákazník si u nás nic nekoupí.
A to vše kvůli takové blbosti, jako je hláška, kdo má dnes svátek. I kdyby se tam zobrazilo ošklivé „Dnes má svátek: null“, pořád by to bylo lepší, než postavit aplikaci mimo provoz (většina uživatelů by si toho „null“ stejně ani nevšimla). Webová aplikace totiž typicky není nemocniční rentgen. Webová aplikace by proto měla být robustní a být v provozu, co jen to jde – a k tomu nám dopomáhej Bůh a kontrolované výjimky :-) Aplikaci bychom se měli snažit udržet v chodu (a umožnit její hlavní funkci – v tomto případě nakupování) i za cenu toho, že některé méně důležité komponenty budou deaktivované (nefunkční). Takového stavu daleko lépe dosáhneme s kontrolovanými výjimkami, které nás kompilátor donutí řešit. Spoléhat se na to, že vývojář bude tak disciplinovaný a svědomitý, že bude odchytávat běhové výjimky na těch správných místech (přestože nemusí), není zrovna šťastné.
Velikost frameworku
Jak už to bývá, každý framework naši aplikaci „nafoukne“ a přidává závislosti. V případě jednoduché ukázkové aplikace došlo k zvětšení .war
souboru z 32 kB na 3 MB. Neříkám, že je to špatně a že bychom frameworky neměli používat – ale vždy bychom měli znát cenu, kterou za jejich použití platíme (a velikost aplikace je její součástí).
Spring je opravdu rozsáhlý framework – JdbcTemplate
představuje jen zlomek jeho možností. Používat Spring jen kvůli JdbcTemplate
je přinejmenším nehospodárné.
Závěr
Někdy se říká, že používat přímo JDBC je „důvod k vyhazovu“. O tomhle bonmotu si myslím své. Jsem přesvědčen, že dobrý vývojář by měl být schopný napsat fungující a bezpečnou datovou vrstvu i s použitím prostého JDBC. A stejně tak by měl znát další možnosti (vyšší úroveň abstrakce), jako je třeba výše zmíněný Spring JdbcTemplate
nebo Hibernate ORM. Volba konkrétního řešení by pak neměla být zatížena tím, že si vývojář zvykl řešit věci „takhle“ nebo naopak že si přečetl nějakou přitroublou poučku a stoprocentně jí uvěřil – měla by to být volba toho nejlepšího možného řešení pro dané použití. A věřte, že smysluplné použití najde kterákoli z těchto variant.
P.S. pokud si chcete přečíst o vývoji (nejen) webových aplikací v Javě, můžete se podívat na můj seriál, který teď vychází na serveru Zdroják.Root.cz.
Odkazy:
- Zdrojové kódy ukázkové aplikace
- Běžící aplikace (jen IPv6)
Trnasakce
Ondrej Medek4:04 odpoledne on Duben 6th, 2011
Hmm, a jak se ridi transakce pres takovy JdbcTemplate, kdyz nechci inicializovat cely Spring kontejner?
Framework
xkucf035:05 odpoledne on Srpen 10th, 2010
Nějaký tip na vhodný framework? Nebo si napsat vlastní?