FK~

Moje odkazy

Ostatní odkazy

Close Windows
Nenajdete mě na Facebooku ani Twitteru
Rozpad EU
Jsem členem FSF
There Is No Cloud …just other people's computers.
Sane software manifesto / Manifest příčetného softwaru

Java na serveru: práce s databází II

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 JdbcTemplateORM Hibernate.

Logo OpenJDK (průhledné 1)

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()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):

Přidání Spring knihovny do projektu v 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?

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.

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:

Přidání Hibernate do Glassfishe

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"> <!-- 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>

Datová vrstva realizovaná pomocí JPA

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.

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 a zdroje:

Témata: [databáze] [Java]

Komentáře čtenářů

Tento článek zatím nikdo nekomentoval

Přidat komentář

reagujete na jiný komentář (zrušit)
jméno nebo přezdívka
název příspěvku
webová stránka, blog
e-mailová adresa
nápověda: možnosti formátování
ochrana proti spamu a špatným trollům

Náhled komentáře