Vediamo in questo appuntamento come modificare la nostra applicazione per fare in modo che utilizzi, per il salvataggio e la lettura dei dati, il database creato nell’articolo precedente.
Configurazione
Per prima cosa dobbiamo creare il file src/main/resources/jdbc.properties con questo contenuto:
[java]jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/arteraspringtutorial
jdbc.username=[utente]
jdbc.password=[password]
[/java]
sostituendo [utente] e [password] con i valori appropriati per il proprio database.
Questo file memorizza i dati di accesso al database, e verrà utilizzato anche dall’applicazione finale; ricordiamo infatti che i file presenti nella directory src/main/resources/ vengono copiati nel classpath, ovvero nella directory /WEB-INF/classes della web application.
Fatto ciò, passiamo a src/main/resources/applicationContext.xml, aggiungendo queste righe:
[xml]
classpath:jdbc.properties
[/xml]
Alcune doverose considerazioni:
- il bean con id=”propertyConfigurer” indica a Spring di caricare le properties contenute in jdbc.properties;
- il bean con id=”dataSource” è un’implementazione di javax.sql.DataSource. L’id “dataSource” è una convenzione di Spring per designare il dataSource dell’applicazione, nel caso ve ne fosse solo uno;
- il bean con id=”transactionManager” definisce il componente che si occuperà di coordinare le transazioni delle classi annotate come @Service o @Transactional.
Spring JDBC
Questa libreria è già inclusa nelle dipendenze del progetto (spring-jdbc) e contiene diverse classi di utilità che consentono di gestire in maniera molto elegante e affidabile le complesse (e decisamente poco eleganti e usabili) JDBC API, consentendo al programmatore di concentrarsi sulla realizzazione delle query e non su aspetti “burocratici”: apertura/chiusura connessioni e delle transazioni, corretta gestione delle eccezioni, iterazione sui risultati, e molto altro.
org.springframework.jdbc.core.JdbcTemplate è la classe più importante di questo package e come molte delle classi di utilità di Spring utilizza il Template Method design pattern. La documentazione ufficiale di Spring la descrive molto bene (nda: traduzione):
Semplifica l’utilizzo di JDBC e aiuta a evitare gli errori più comuni. Si occupa di gestire correttamente il workflow delle JDBC API, lasciando al codice applicativo il compito di fornire l’SQL e di estrarre i risultati. Questa classe esegue query o update SQL, inizializzando l’iterazione sui ResultSet, catturando le eccezioni JDBC e traducendole nelle corrispettive generiche e più informative definite nel package org.springframework.dao.
Chi ha anche solo un minimo di esperienza con le macchinose JDBC API ha certamente idea di cosa vuol dire gestirne correttamente il workflow e siamo sicuri che apprezzerà le funzionalità fornite da JdbcTemplate!
InsertionDao
Per chi voglia implementare dei DAO utilizzando le JDBC API, Spring mette a disposizione una classe astratta: org.springframework.jdbc.core.support.JdbcDaoSupport. Estendendola, e configurando un dataSource, avremo a disposizione un DAO con un’istanza già pronta e funzionante di JdbcTemplate: nessun bisogno di configurare alcunché, nessun bisogno di specificare transazioni (gestite da Spring), nessuna gestione di eccezioni JDBC. Molto comodo!
Ecco quindi come cambia la nostra implementazione di it.artera.springtut.dao.InsertionDao:
[java]package it.artera.springtut.dao;
import it.artera.springtut.model.Insertion;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Repository;
@Repository
public class InsertionDao extends JdbcDaoSupport {
private RowMapper<?> insertionRowMapper;
public InsertionDao() {
this.insertionRowMapper = new InsertionRowMapper();
}
@Autowired
public void init(DataSource dataSource) {
setDataSource(dataSource);
}
/**
* Carica gli ultimi 5 annunci inseriti nel database
* @return
*/
@SuppressWarnings("unchecked")
public List<Insertion> getMostRecentInsertions() {
StringBuilder sql = new StringBuilder(255);
sql.append("select");
addSelectFields(sql);
sql.append(" from insertions as i");
sql.append(" order by i.creationDate desc");
sql.append(" limit 5");
return (List<Insertion>) getJdbcTemplate().query(sql.toString(), insertionRowMapper);
}
/**
* Carica l’inserzione con l’ID specificato
* @param id
* @return
*/
public Insertion getInsertion(long id) {
StringBuilder sql = new StringBuilder(255);
sql.append("select");
addSelectFields(sql);
sql.append(" from insertions as i");
sql.append(" where id = " + id);
return (Insertion) getJdbcTemplate().queryForObject(sql.toString(), insertionRowMapper);
}
private void addSelectFields(StringBuilder sql) {
sql.append(" i.id, i.title, i.description, i.photo, i.price, i.creationDate");
}
}
[/java]
Da notare:
- le string SQL vengono create tramite degli StringBuilder per la massima flessibilità;
- ai metodi query() e queryForObject() di JdbcTemplate viene passata un’istanza di RowMapper (vedi oltre per il sorgente). Questa verrà utilizzata per mappare correttamente i dati contenuti nel ResultSet nelle property delle istanze di oggetti Insertion;
Ecco quindi il sorgente di it.artera.springtut.dao.InsertionRowMapper, l’implementazione di RowMapper specifica per la nostra classe:
[java]package it.artera.springtut.dao;
import it.artera.springtut.model.Insertion;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
/**
* Utilizzato per mappare le SELECT sql su oggetti Java, l’ordine dei campi
* è lo stesso presente nella classe java {@link Insertion}
*/
class InsertionRowMapper implements RowMapper<Insertion> {
public Insertion mapRow(ResultSet rs, int rowNum) throws SQLException {
Insertion insertion = new Insertion();
int i = 1;
insertion.setId(rs.getLong(i++));
insertion.setTitle(rs.getString(i++));
insertion.setDescription(rs.getString(i++));
insertion.setPhoto(rs.getString(i++));
insertion.setPrice(rs.getBigDecimal(i++));
insertion.setCreationDate(rs.getDate(i++));
return insertion;
}
}
[/java]
Verifica
È arrivato il momento di fare una verifica, possiamo lanciare tutti i test dell’applicazione con il comando:
mvn test
oppure possiamo semplicemente verificare il solo InsertionDao eseguendo la classe di test InsertionDaoTest:
mvn test -Dtest=InsertionDaoTest
Se avete seguito correttamente le istruzioni, dovreste avere un bel messaggio BUILD SUCCESSFUL. Abbiamo infatti modificato solamente l’implementazione del nostro DAO, ma non l’interfaccia; quindi, a rigor di logica, i test (e in generale chi usa il DAO) dovrebbero continuare a funzionare normalmente, senza richiedere alcuna modifica.
Linkografia
- JDBC Technology – official documentation (Oracle – ex Sun);
- Template Method design pattern;