Moje odkazy
Obsah článku:
vydáno: 11. 2. 2010 11:04, aktualizováno: 22. 9. 2014 16:54
V předchozím díle jsme začali téma práce s databází,
naučili jsme se k ní přistupovat pomocí JSP značek a napsat si vlastní zjednodušenou DAO vrstvu.
Dnes se budeme věnovat dvěma pokročilejším způsobům přístupu k databázi – použití třídy JdbcTemplate
a ORM Hibernate.
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.
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 teleshopingu, takže nebudu zastírat i tu druhou stranu mince. Jak tedy k úspoře kódu došlo?
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í.
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).
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.
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í:
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"> <!-- jméno persistentní jednotky -->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>jdbc/nekurak</jta-data-source> <!-- JNDI jméno -->
<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>
S daty pracujeme pomocí správce entit – 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.
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.
Tento článek zatím nikdo nekomentoval