Spring JDBC
Detta inlägg ingår i serien Spring från början och kommer att behandla hur man kan jobba med JdbcTemplate
och andra centrala klasser i modulen spring-jdbc.
Templating
Ett designmönster som används flitigt i Spring är ”template method”. En template-metod definierar ett skelett för en algoritm. Algoritmen är abstrakt och det är upp till subklasser att definiera det konkreta beteendet för algoritmen. Ett annat designmönster som påminner mycket om ”template method” är mönstret Strategy, en av skillnaderna mellan ”template method” och ”Strategy” är att ”template method” använder arv medan Strategy-mönstret använder delegat för att uppnå sitt mål. Nedanstående visar användningen av Strategy-mönstret via ett exempel som är kraftigt influerat från ett wikipedia-exempel som använder template method.
/** A game that can be played... */
public interface Game {
void initializeGame();
void makePlay(int player);
boolean endOfGame();
void printWinner();
}
/** Concrete implementation of the chess game... */
public class Chess implements Game {
public void initializeGame() {
// do chess stuff...
}
public void makePlay(int player) {
// do chess stuff...
}
public boolean endOfGame() {
// do chess stuff...
return false;
}
public void printWinner() {
// do chess stuff...
}
}
/** Concrete implementation of the monopoly game... */
public class Monopoly implements Game {
public void initializeGame() {
// do monopoly stuff...
}
public void makePlay(int player) {
// do monopoly stuff...
}
public boolean endOfGame() {
// do monopoly stuff...
return false;
}
public void printWinner() {
// do monopoly stuff...
}
}
/** The class containing the template method. */
public class GameTemplate {
/**
* This is a template method that plays the provided
* game using a specified algorithm (initialize,
* play, end, print winner)
*
* The template method is common to several games in
* which players play against the others, but only one
* player is playing at a given time.
*/
public void play(int playersCount, Game game) {
game.initializeGame();
int j = 0;
while (!game.endOfGame()){
game.makePlay(j);
j = (j + 1) % playersCount;
}
game.printWinner();
}
}
Exemplet ovan visar hur en algoritm appliceras på ett Game
-objekt i metoden play
. Exemplet är en aning förändrat jämfört med originalet så till vida att klassen GameTemplate
inte är ett Game
utan endast innehåller själva algoritmen för att få en lösare koppling mellan algoritmen och de faktiska Game
-objekten.
Nu när vi känner till designmönstret ”template method” kan vi kika vidare på templaten som tillhandahålls av Spring för JDBC-relaterade operationer, nämligen JdbcTemplate
.
JdbcTemplate
Klassen JdbcTemplate
är kärnan i den modul som kallas spring-jdbc. Klasserna i denna modul används när access till databasen sker via JDBC snarare än via ett O/R-mappningsramverk såsom Hibernate.
Att använda rå JDBC kan medföra en hel del problem för utvecklaren där Connections, Statements, ResultSets måste stängas, exceptions måste fångas osv. Kod som är skriven för rå JDBC-användning är ofta väldigt utförlig (verbose) och leder lätt till fel (såsom att glömma att stänga ett statement). Springs JdbcTemplate
hjälper utvecklaren med många delar av de uppgifter som krävs för att använda JDBC.
Task | Spring | You |
---|---|---|
Connection Management | X | |
SQL | X | |
Statement Management | X | |
ResultSet Management | X | |
Row Data Retrieval | X | |
Parameter Declaration | X | |
Parameter Setting | X | |
Transaction Management | X |
Nedanstående exempel visar vanlig användning av rå JDBC med en enkel parameteriserad SELECT och en mappning mellan ett Person
-objekt och ett ResultSet
.
private static final String PERSON_QUERY =
"select first_name, last_name, phone_number "
+ "from person where last_name = ?";
public List getPersonList1() {
List list = new ArrayList();
Connection connection = null;
PreparedStatement statement = null;
try {
connection = getConnection();
statement = connection.prepareStatement(PERSON_QUERY);
statement.setString(1, "Svensson");
ResultSet rs = statement.executeQuery();
while (rs.next()) {
Person person = new Person(
rs.getString("first_name"),
rs.getString("last_name"),
rs.getString("phone_number"));
list.add(person);
}
rs.close();
} catch (SQLException e) {
// handle exception somehow...
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
// handle exception somehow...
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
// handle exception somehow...
}
}
}
return list;
}
Exemplet kan förenklas avsevärt genom användning av JdbcTemplate
vilket metoden getPersonList2
i nedanstående exempel visar.
public List getPersonList2 () {
RowMapper personMapper = new RowMapper() {
// Maps a row to a Person
public Object mapRow(
ResultSet rs,
int rowNum) throws SQLException {
return new Person(
rs.getString("first_name"),
rs.getString("last_name"),
rs.getString("phone_number"));
}
};
return getJdbcTemplate().query(
PERSON_QUERY, new Object[] {"Svensson"}, personMapper);
}
Vi antar i exemplet ovan att man kan få tag på en JdbcTemplate
via metoden getJdbcTemplate()
.
Några saker värda att notera:
- Kod för att hantera connections, statements etc. saknas helt. Detta hanteras av
JdbcTemplate
som öppnar och stänger alla resurser. - Mappning mellan SQL-resultatet och affärsobjektet sker i en
RowMapper
. Detta är ett tydligt mönster ochRowMapper
-objektet kan återanvändas om det deklareras utanför metoden. FörutomRowMapper
finns även andra sätt att mappa data och dessa kan återfinnas i Springdokumentationen. - Ingen felhantering krävs. Alla eventuella exceptions fångas av
JdbcTemplate
och konverteras sedan till Springs egna unchecked exceptions mha.SQLExceptionTranslator
. Istället för att kasta ett checked exception med en kryptisk databasunik felkod kan tydliga exceptions såsomBadSqlGrammarException
ellerOptimisticLockingFailureException
kastas. De databaser som supportas out-of-the-box är DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL och Sybase. Naturligtvis går det även att plugga in egnaSQLExceptionTranslators
.
Det finns även några inbyggda RowMappers
. Metoden queryForInt
som exemplifieras nedan skapar en intern RowMapper
där ett SQL-resultat mappas mot ett heltal.
public int countPeople() {
return getJdbcTemplate().queryForInt(
"select count(*) from person ");
}
I exemplen ovan har endast SELECT-frågor mot databasen ställts. Det finns även metoder för att utföra uppdateringar, se exemplet nedan för en enkel parameteriserad uppdatering:
private static final String UPDATE_PERSON =
"update person set phone_number = ?"
+ "where first_name = ? and last_name = ?";
public void updatePhoneNumber(
String firstName, String lastName, String phoneNumber) {
getJdbcTemplate().update(
UPDATE_PERSON,
new Object[] {phoneNumber, firstName, lastName});
}
JdbcTemplate
behöver endast konfigureras upp med en datakälla. Det finns några bekväma klasser att ärva från för att enkelt få tag på JdbcTemplate
-objektet. Klassen JdbcDaoSupport
kan användas för DAO-objekt medan klassen AbstractTransactionalDataSourceSpringContextTests
fungerar bra för JUnit-tester. Nedanstående exempel visar detta.
Så här kan en DAO-klass se ut:
public class MyDao extends JdbcDaoSupport {
private static final String UPDATE_PERSON =
"update person set phone_number = ? "
+ "where first_name = ? and last_name = ?";
public void updatePhoneNumber (
String firstName, String lastName, String phoneNumber) {
getJdbcTemplate().update(
UPDATE_PERSON,
new Object[] {phoneNumber, firstName, lastName});
}
}
När en DAO konfigureras så måste en DataSource
injectas, detta kan exempelvis ske via en JNDI-uppslagning:
<bean
id="myDataSource"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="lookupOnStartup" value="false" />
<property name="proxyInterface" value="javax.sql.DataSource" />
<property name="resourceRef" value="true" />
<property name="jndiName" value="jdbc/myds" />
</bean>
Själva DAO-objekten kan exempelvis deklareras i en egen fil som kan användas både för produktion och för test.
<bean id="myDao" class="se.cygni.sample.spring.template.jdbc.MyDao">
<property name="dataSource" ref="dataSource" />
</bean>
När applikationen ska konfigureras för test så kan en DataSource
konfigureras utan JNDI-uppslagning:
<bean
id="myDataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
<property name="driverClassName"
value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@host:port:schema" />
<property name="username" value="scott" />
<property name="password" value="tiger" />
</bean>
Ett JUnit-test kan se ut enligt följande:
public class MyDaoTest
extends AbstractTransactionalDataSourceSpringContextTests {
private MyDao dao;
protected String[] getConfigLocations() {
return new String[] {
"testDataSourceContext.xml",
"daoContext.xml"
};
}
public void testDao() {
dao.updatePhoneNumber("Johnny", "Puma", "555-555 5555");
int count = getJdbcTemplate().queryForInt(
"select count(*) from person "
+ "where first_name = 'Johnny' "
+ "and last_name = 'Puma' "
+ "and phone_number = '555-555 5555'");
assertTrue(count == 1);
}
public void setMyDao(MyDao dao) {
this.dao = dao;
}
}
I testet ovan visas hur dels MyDao
injectas i testklassen eftersom filen daoContext.xml
anges som en config-location. Även filen testDataSourceContext.xml
inkluderas vilket leder till att en DataSource
finns tillgänglig och då injectas denna DataSource
i basklassen AbstractTransactionalDataSourceSpringContextTests
vilket leder till att metoden getJdbcTemplate
blir tillgänglig i testfallet.
Det finns även stöd för Java 5 i Springs JDBC modul. Genom att ärva klassen SimpleJdbcDaoSupport
istället för JdbcDaoSupport
i din DAO kan Java 5 metoder användas via klassen SimpleJdbcTemplate. Se exemplet nedan.
public List<Person> getPeople() {
ParameterizedRowMapper<Person> rowMapper =
new ParameterizedRowMapper<Person>() {
public Person mapRow(ResultSet rs, int rowNum)
throws SQLException {
return new Person(
rs.getString("first_name"),
rs.getString("last_name"),
rs.getString("phone_number"));
}
};
return getSimpleJdbcTemplate().query(
"select first_name, last_name, phone_number from person",
rowMapper);
}
För mer information om Spring och JDBC rekommenderas Springdokumentationen och JavaDoc för JdbcTemplate.