ComiaTip Tietokantaohjelmointi springillä


Tietokantaohjelmointi springillä

Kirjoittanut Mika Julkaistu 16.2.2009

Alkutilanne:

Sovelluksessa on käyttäjää kuvastava Domain-luokka nimeltään User

public class User { private long id; private String username; private String name; //getterit ja setterit... }

Tietokannassa käyttäjä on tarkoitus tallentaa seuraavanlaiseen tauluun:

Table "public.sample_user"

Column | Type | Modifiers ----------+------------------------+----------- id | bigint | not null username | character varying(50) | not null name | character varying(255) | not null

Indexes: "pk" PRIMARY KEY, btree (id)

Tietokantaan voidaan lisätä esimerkkidataa, jotta hakuja voidaan testata. Esimerkiksi:

id | username | name ----+----------+--------------- 2 | tim | Tim Callahan 1 | hank | Hank da Silva

DAO-malli

Tietokannan kanssa kommunikointi hoidetaan DAO-mallilla (Data Access Object), jossa kullekin domain-luokalle on oma DAO-luokka joka hoitaa tietokantaoperaatiot ko. luokkaan liittyen. Alla kuvattuna Käyttäjän hakemiseen käytety DAO-luokan rajapinta.

public interface UserDAO { void setDataSource(DataSource dataSource); User read(long id); //muita hakumetodeja ja luonti/päivitysmetodit ... }

Mallissa oletetaan että DAO-oliot saavat javax.sql.DataSource-olion, jolta tietokantayhteyden voi hakea ennen kuin hakumetodeja kutsutaan. DataSourcen asettaminen jätetään sovelluksessa Spring -frameworkin huoleksi. Näin dao-koodin ei tarvitse huolehtia tietokantayhdeyden noutamiseen liittyvistä yksityiskohdista.

Konfigurointi

Tietokantayhteys määritellään spring kontekstissa, esimerkisi seuraavaan tapaan (Käytetään apache commons DBCP-tietokantayhteysallasta ja postgresql-tietokantaa) :

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="org.postgresql.Driver" /> <property name="url" value="jdbc:postgresql://localhost:5432/springSample" /> <property name="username" value="user" /> <property name="password" value="salasana" /> </bean>

Tämän jälkeen DAO:n toteutus esitellään samaisesse spring-kontekstissa, samalla konfiguroidaan Spring asettamaan daolle dataSource olio property-elementillä. propertyn name-attribuutti kertoo mitä setteri-metodia oliolta kutsutaan (setDataSource) ja ref määrittää olion joka asetetaan (tässä tapauksessa äsken esitelty dataSource).

<bean id="userDAO" class="springsample.user.UserDAOImpl"> <property name="dataSource" ref="dataSource"/> </bean>

DAOn toteutus

Tässä tapauksessa DAO:n toteuteutuksessa käytettään Springin mukana tulevaa SimpleJdbcTemplate-luokkaa. Se on template method -suunnittelumallin mukainen luokka, jota voidaan käyttää yksinkertaistamaan JDBC-operaatioita.

public class UserDAOImpl implements UserDAO { private SimpleJdbcTemplate template; public void setDataSource(DataSource dataSource) { this.template = new SimpleJdbcTemplate(dataSource); [1] } public User read(long id) { return template.queryForObject( "SELECT * FROM sample_user WHERE id=?", new UserMapper(), id); [2] } //muita haku-ja kirjoitusmetodeja ... private static class UserMapper implements ParameterizedRowMapper<User> { [3] public User mapRow(ResultSet rs, int rowNum) throws SQLException { User u = new User(); u.setId(rs.getLong("id")); u.setUsername(rs.getString("username")); u.setName(rs.getString("name")); return u; } } }

SimpleJdbcTemplate luodaan kun dataSource astetetaan oliolle (spring kontekstin toimesta) [1]. Templaten käyttö yksinkertaistaa JDBC-koodia huomattavasti [2]. Tarvitaan vain sql-lause, RowMapper -olio, joka huolehtii resultsetin muuttamisesta olioksi (tässä toteutettu sisäluokkana [3] ) ja Statementille annettavat parametrit.

Vertailun vuoksi alla vastaava toteutus toteutettuna perinteiseen tapaan:

public class UserPlainJdbcDAO implements UserDAO { private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public User read(long id) { User u = null; Connection connection = null; try { connection = dataSource.getConnection(); PreparedStatement ps = connection.prepareStatement( "SELECT * FROM sample_user WHERE id=?"); ps.setLong(1, id); ResultSet rs = ps.executeQuery(); if (rs.next()) { u = new User(); u.setId(rs.getLong("id")); u.setUsername(rs.getString("username")); u.setName(rs.getString("name")); } } catch (SQLException ex) { Logger.getLogger(UserPlainJdbcDAO.class. getName()).log(Level.SEVERE, null, ex); throw new RuntimeException("exception while"+ " reading user", ex); } finally{ if (connection!=null) try { connection.close(); } catch (SQLException ex) { Logger.getLogger(UserPlainJdbcDAO.class. getName()).log(Level.SEVERE, null, ex); } } return u; } //muita haku- ja kirjoitusmetodeja... }

JdbcTemplate vähentää huomattavasti toistuvaa koodia, varsinkin kun RowMapperia pystyy uudelleenkäyttämään kaikissa hakumetodeissa. Templatea käyttäessä tulee samalla käytettyä springin hienostunutta tietokantayhteyksien poikkeuksien hallintaa ja tulkkausta.

Jos sovelluksessa haluttaisiin vaihtaa käyttäjän lukemiseen käytettävän daon toteutusta onnistuisi se helposti vaihtamalla luokan nimi userDAO beanin: class-attribuuttiin:

<bean id="userDAO" class="springsample.user.UserPlainJdbcDAO"> <property name="dataSource" ref="dataSource"/> </bean>

DAO käytössä

Spring kontekstin käynnistäminen onnistuu helposti seuraavalla koodilla:

Sovelluksen ajamiseksi classpathissa on oltava juuri luodut luokat, springin kirjastot ja kontekstitiedosto beans.xml.

Alla tiedosto vielä kokonaisena:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/ spring-beans-2.5.xsd"> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="org.postgresql.Driver" /> <property name="url" value="jdbc:postgresql://localhost:5432/springSample" /> <property name="username" value="user" /> <property name="password" value="salasana" /> </bean> <bean id="userDAO" class="springsample.user.UserDAOImpl"> <property name="dataSource" ref="dataSource"/> </bean> </beans>

Tämän jälkeen konteksti on valmis otettavaksi käyttöön esimerkiksi seuraavalla koodilla.

public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml"); [1] UserDAO dao = (UserDAO) ctx.getBean("userDAO"); [2] System.out.println(dao.read(1).getName()); [3] }

Spring -konteksti on tässä tallennettuna beans.xml-nimiseen xml-tiedostoon, se voidaan ladata luomalla uusi ClassPathXmlApplicationContext-olio [1]. DAO noudetaan kontekstilta id:n perusteella (bean-elementin id-attribuutti) [2]. Tämän jälkeen dao onkin valmis käytettäväksi [3], Spring -konteksti on automaattisesti asettanut sen riippuvuudet (tässä tapauksessa datasourcen).

Huomattavaa on että ratkaisulla ollaan kyetty piilottamaan käyttäjän tietokantaoperaatioiden konkreettinen toteutus UserDAO-rajapinnan taakse. Springin mukanaan tuoman Dependency Injection -mallin vuoksi DAOa käyttävän koodin ei tarvitse kutsua new-operaattoria, joten se ei myöskään tarvitse viittausta konkreettiseen DAO-toteutukseen, pelkkä rajapinta riittää. Näin järjestelmän logiikkakerros voidaan rakentaa irralliseksi tietokantakerroksesta ja siten saavutetaan löyhät riippuvuudet komponenttien välillä. DAO-luokkiin tulevat muutokset eivät enää heijastu niin helposti logiikkakerroksiin, kunhan rajapinta pysyy ennallaan niin daojen koodia voidaan turvallisesti muuttaa.

Kurssilla Monikerrosarkkitehtuurin hallinta Springillä jatketaan aiheesta esimerkiksi siihen, kuinka DAO-komponentti liitetään palvelu/logiikka -kerrokseen ja kuinka helposti se saadaan toimimaan osana suurempaa transaktiota.