Jak jste si asi všimli v minulém díle, psát datovou vrstvu jen s použitím základního JDBC vyžaduje poměrně dost nudného a opakujícího se kódu. To znamená jednak práci navíc a jednak potenciální chybovost – čím víc kódu, tím víc míst, kde jsme mohli udělat chybu. Tyto problémy samozřejmě postupem času řešíme, optimalizujeme, vyčleňujeme opakující se kód do znovupoužitelných tříd… až najednou zjistíme, že si píšeme vlastní framework. Někdy je to správná cesta, jindy je ale lepší, soustředit se na jádro naší aplikace (obchodní logiku) a pro datovou vrstvu použít raději už hotový framework. V následujícím textu si proto ukážeme, jak využít kód, který za nás napsal někdo jiný – Hibernate a Spring.
Jen pro připomenutí: jako obvykle si z Mercurialu stáhneme aktuální verzi zdrojových kódů k dnešnímu dílu seriálu:
$ hg pull $ hg up "4. díl"
Případně si je můžete stáhnout jako bzip2 archiv přes web.
Pomocník JdbcTemplate
Třída JdbcTemplate
pochází z frameworku Spring. Jedná se o velmi rozsáhlý framework a JdbcTemplate
představuje jen zlomek jeho možností. Základní kostru třídy PodnikDAO
necháme stejnou a upravovat budeme jen vnitřek metod getPodniky()
a ulozPodnik()
. Prezentační vrstva JSP a JavaBean tak může zůstat nezměněná.
Do webového projektu si přidáme knihovnu Spring Framework (již obsažena ve vaší instalaci Netbeans):
Díky použití JdbcTemplate
dojde k výrazné úspoře kódu (přinejmenším na první pohled). Z původních devatenácti řádků:
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); } }
Na pouhý jeden:
public Collection<Podnik> getPodniky() { return jdbcTemplate.query(getSQL(SQL.SELECT_VSECHNY), podnikRowMapper); }
Řekněte, není to skvělé? Je to skvělé! …bohužel tady nejsme v teleshoppingu, takže nebudu zastírat i tu druhou stranu mince. Jak tedy k úspoře kódu došlo?
Mapování pomocí RowMapperu
Jistě jste si všimli proměnné podnikRowMapper
– ta odkazuje na instanci třídy PodnikRowMapper
, kterou jsme si museli napsat. Třída implementuje springové rozhraní ParameterizedRowMapper
a vypadá následovně:
… public class PodnikRowMapper implements ParameterizedRowMapper<Podnik> { public Podnik mapRow(ResultSet rs, int i) throws SQLException { Podnik p = new Podnik(); p.setId(rs.getInt("id")); p.setNazev(rs.getString("nazev")); return p; } }
RowMapper se stará o vytažení hodnot z SQL výsledkové sady a jejich naplnění do instance požadované třídy. Výhodný je tento přístup zejména tehdy, když máme více metod pro načítání téhož typu objektů – např. jednou vracíme kolekci všech záznamů, jindy jen jeden konkrétní nebo podmnožinu – potom máme mapovací kód pěkně na jednom místě a když třeba přidáme do tabulky nový sloupeček, změnu v datové vrstvě děláme jen na jednom místě. Pro každou třídu/tabulku potřebujeme jeden RowMapper.
Jedná se vlastně o takový předstupeň ORM (objektově-relačního mapování), ovšem funguje jen pro načítání dat a ne jejich ukládání.
Pozor na nekontrolované výjimky
Další věc, které si nelze nevšimnout, je absence odchytávání výjimek. Spring totiž převádí kontrolované SQL výjimky na běhové (nekontrolované). Běhové výjimky nemusíme odchytávat (resp. kompilátor nás k tomu nedonutí), a tak chyba vyletí tak vysoko, kam až ji pustíme.
Pokud tedy nejsme dostatečně svědomití a nedoplníme dobrovolně kód pro ošetření chyb, odchytí výjimku až aplikační server a k uživateli se dostane v podobě standardní 500 HTTP chybové stránky. Už ve druhém díle jsme se naučili psát vlastní chybové stránky – pokud si je tedy nezapomeneme nastavit, k uživateli se až tak ošklivá chybová hláška nedostane. Přesto bychom na odchytávání výjimek neměli úplně rezignovat a ušetřené try { … } catch ( … ) { … }
se nám přesunou jen do jiné části aplikace (ale mohou být centralizované a nemusí se tolik opakovat).
Velikost aplikace
Jak už to u frameworků bývá, zvyšují datovou velikost naší aplikace. V tomto konkrétním případě vzrostla velikost souboru nekurak.net-web.war
(zkompilovaná aplikace) z 32 kilobajtů na úctyhodné 3 megabajty. Spring nabízí opravdu mnohem víc než jen JdbcTemplate
a když už si ho do své aplikace zavlečete, bylo by škoda využívat z jeho potenciálu jen tak málo.
Zaujaly vás možnosti Javy a chcete se dozvědět o tomto jazyce víc? Akademie Root nabízí školení Základy programovacího jazyka Java a Pokročilejší kurz jazyka Java, na nichž se naučíte, jak tento multiplatformní objektově orientovaný jazyk používat.
Hibernate ORM
Hibernate je middleware pro objektově-relační mapování a persistenci dat. Použití ORM nám může ušetřit spoustu duplicitního a nudného kódu, ale i přidělat starosti, je to horké téma nejen do internetových diskusí. V článku se této polemice raději vyhnu (můžeme diskutovat pod ním) a podíváme se na praktický příklad – jednoduchou ukázku použití Hibernatu.
Nejprve si doinstalujeme podporu Hibernate do našeho Glassfishe. Pomocí webového rozhraní a nástroje Update Tool:
Glassfish si potřebné knihovny sám stáhne a potom je potřeba aplikační server restartovat.
S Hibernatem nebudeme pracovat přímo, ale pomocí tzv. Java Persistence API (JPA), což je abstraktní vrstva a Hibernate je jen jednou z několika implementací ORM, které v JPA můžeme používat (další jsou třeba TopLink nebo OpenJPA).
Poznámka: pro potřeby persistence jsem trochu přeuspořádal náš projek, nyní se skládá ze čtyř částí:
- nekurak.net-ear – zastřešující „enterprise“ projekt, který budeme nasazovat na server (obsahuje v sobě níže uvedené projekty)
- nekurak.net-war – původní webová vrstva: JSP a JavaBeany
- nekurak.net-ejb – EJB vrstva: zde budeme pracovat s Hibernatem
- nekurak.net-lib – společné knihovny – DTO a rozhraní
Konfigurace a mapování
Nejdůležitějším konfiguračním souborem je persistence.xml
, ve kterém definujeme tzv. persistentní jednotku (PU) a JNDI jméno datového zdroje, který bude používat:
<?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> <persistence-unit name="nekurak.net-PU" transaction-type="JTA"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>jdbc/nekurak</jta-data-source> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/> <property name="hibernate.hbm2ddl.auto" value="validate"/> <property name="hibernate.max_fetch_depth " value="3"/> <property name="hibernate.default_batch_fetch_size" value="16"/> <property name="hibernate.order_updates" value="true"/> <property name="hibernate.order_inserts" value="true"/> <property name="hibernate.show_sql" value="false"/> </properties> </persistence-unit> </persistence>
Dále musíme provést vlastní mapování tabulek relační databáze na objekty. K tomu se používají buď anotace uvnitř javových tříd, nebo XML soubory. Mapování pomocí XML vypadá následovně – soubor Podnik.hbm.xml
:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="cz.frantovo.nekurak.dto.Podnik" table="podnik"> <id name="id" column="id" type="integer"/> <property name="nazev" column="nazev"/> </class> </hibernate-mapping>
Datová vrstva realizovaná pomocí JPA
S daty pracujeme pomocí „entitního manažera“ – použití vidíte ve třídě PodnikHibernateDAO
:
@Stateless public class PodnikHibernateDAO implements PodnikHibernateDAORemote { @PersistenceContext(unitName = "nekurak.net-PU") private EntityManager em; public Collection<Podnik> getPodniky() { Query dotaz = em.createQuery("FROM " + t(Podnik.class) + " o ORDER BY nazev"); return dotaz.getResultList(); } private static String t(Class trida) { return trida.getSimpleName(); } }
K dotazování používáme jiný jazyk než SQL – EJB-QL resp. JPQL. Tento jazyk má daleko blíže k javovým objektům než k relačním tabulkám, proto není až tak užitečné vyčleňovat ho do samostatných souborů, jako jsme to dělali s SQL. Dotazy můžeme psát jako obyčejné textové řetězce, ale můžeme je i poskládat z názvů tříd – viz metoda t()
– díky tomu můžeme na dotazy používat refaktoring. Pokud bychom se např. rozhodli přejmenovat třídu Podnik
na Hospoda
, stačí ji refaktorovat a nemusíme ručně procházet všechny dotazy. Přehlednější a užitečnější zápis nechť si vybere každý sám.
Stejně jako v případě Springu se jedná o velmi rozsáhlou problematiku a každé z těchto témat by vydalo na samostatný seriál. Proto tento díl berte hlavně jako nástin možností a inspiraci k dalšímu studiu.
Závěr
Volba frameworku je vždy obtížné rozhodnutí a pokud situaci řešíte týmově, vstupují do hry navíc i rozdílné osobní preference jednotlivých kolegů. Neexistuje univerzální řešení a tohle rozhodnutí za vás nikdo neudělá – musíte vycházet ze svých zkušeností, z požadavků konkrétního projektu a znalostí vývojářů. Věřím, že čtyři možnosti nastíněné v tomto a předchozím díle vám s rozhodováním pomůžou.
V komentářích se prosím vyjádřete, jaká další témata by vás zajímala – v plánu jsou např. autorizace/autentizace, lokalizace, výstupní formátování, EJB.
Odkazy
- Spring Framework – Reference Documentation – dokumentace ke Springu 2.5.
- Spring Framework – Reference Documentation – dokumentace ke Springu 3.0.
- Hibernate Documentation Overview – přehled dokumentace k Hibernatu.
- EJB-QL examples – příklady dotazovacího jazyka EJB-QL (budete potřebovat místo SQL).
- Java Persistence API – základní informace o JPA.
Přehled komentářů