JdbcTemplate
是core包的核心類(lèi)。它替我們完成了資源的創(chuàng)建以及釋放工作,從而簡(jiǎn)化了我們對(duì)JDBC的使用。它還可以幫助我們避免一些常見(jiàn)的錯(cuò)誤,比如忘記關(guān)閉數(shù)據(jù)庫(kù)連接。JdbcTemplate將完成JDBC核心處理流程,比如SQL語(yǔ)句的創(chuàng)建、執(zhí)行,而把SQL語(yǔ)句的生成以及查詢結(jié)果的提取工作留給我們的應(yīng)用代碼。它可以完成SQL查詢、更新以及調(diào)用存儲(chǔ)過(guò)程,可以對(duì)ResultSet
進(jìn)行遍歷并加以提取。它還可以捕獲JDBC異常并將其轉(zhuǎn)換成org.springframework.dao
包中定義的,通用的,信息更豐富的異常。
使用JdbcTemplate進(jìn)行編碼只需要根據(jù)明確定義的一組契約來(lái)實(shí)現(xiàn)回調(diào)接口。PreparedStatementCreator
回調(diào)接口通過(guò)給定的Connection
創(chuàng)建一個(gè)PreparedStatement,包含SQL和任何相關(guān)的參數(shù)。CallableStatementCreateor
實(shí)現(xiàn)同樣的處理,只不過(guò)它創(chuàng)建的是CallableStatement。RowCallbackHandler
接口則從數(shù)據(jù)集的每一行中提取值。
我們可以在一個(gè)service實(shí)現(xiàn)類(lèi)中通過(guò)傳遞一個(gè)DataSource
引用來(lái)完成JdbcTemplate的實(shí)例化,也可以在application context中配置一個(gè)JdbcTemplate bean,來(lái)供service使用。需要注意的是DataSource
在application context總是配制成一個(gè)bean,第一種情況下,DataSource
bean將傳遞給service,第二種情況下DataSource
bean傳遞給JdbcTemplate bean。因?yàn)镴dbcTemplate使用回調(diào)接口和SQLExceptionTranslator
接口作為參數(shù),所以一般情況下沒(méi)有必要通過(guò)繼承JdbcTemplate來(lái)定義其子類(lèi)。
JdbcTemplate中使用的所有SQL將會(huì)以“DEBUG”級(jí)別記入日志(一般情況下日志的category是JdbcTemplate
相應(yīng)的全限定類(lèi)名,不過(guò)如果需要對(duì)JdbcTemplate
進(jìn)行定制的話,可能是它的子類(lèi)名)。
11.2.2. NamedParameterJdbcTemplate
類(lèi)
NamedParameterJdbcTemplate
類(lèi)增加了在SQL語(yǔ)句中使用命名參數(shù)的支持。在此之前,在傳統(tǒng)的SQL語(yǔ)句中,參數(shù)都是用'?'
占位符來(lái)表示的。 NamedParameterJdbcTemplate
類(lèi)內(nèi)部封裝了一個(gè)普通的JdbcTemplate
,并作為其代理來(lái)完成大部分工作。下面的內(nèi)容主要針對(duì)NamedParameterJdbcTemplate
與JdbcTemplate
的不同之處來(lái)加以說(shuō)明,即如何在SQL語(yǔ)句中使用命名參數(shù)。
通過(guò)下面的例子我們可以更好地了解NamedParameterJdbcTemplate
的使用模式(在后面我們還有更好的使用方式)。
// some JDBC-backed DAO class...
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(0) from T_ACTOR where first_name = :first_name";
NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(this.getDataSource());
SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
return template.queryForInt(sql, namedParameters);
}
在上面例子中,sql
變量使用了命名參數(shù)占位符“first_name”,與其對(duì)應(yīng)的值存在namedParameters
變量中(類(lèi)型為MapSqlParameterSource
)。
如果你喜歡的話,也可以使用基于Map風(fēng)格的名值對(duì)將命名參數(shù)傳遞給NamedParameterJdbcTemplate
(NamedParameterJdbcTemplate
實(shí)現(xiàn)了NamedParameterJdbcOperations
接口,剩下的工作將由調(diào)用該接口的相應(yīng)方法來(lái)完成,這里我們就不再贅述):
// some JDBC-backed DAO class...
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(0) from T_ACTOR where first_name = :first_name";
NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(this.getDataSource());
Map namedParameters = new HashMap();
namedParameters.put("first_name", firstName);
return template.queryForInt(sql, namedParameters);
}
另外一個(gè)值得一提的特性是與NamedParameterJdbcTemplate
位于同一個(gè)包中的SqlParameterSource
接口。在前面的代碼片斷中我們已經(jīng)看到了該接口的實(shí)現(xiàn)(即MapSqlParameterSource
類(lèi)),SqlParameterSource
可以用來(lái)作為NamedParameterJdbcTemplate
命名參數(shù)的來(lái)源。MapSqlParameterSource
類(lèi)是一個(gè)非常簡(jiǎn)單的實(shí)現(xiàn),它僅僅是一個(gè)java.util.Map
適配器,當(dāng)然其用法也就不言自明了(如果還有不明了的,可以在Spring的JIRA系統(tǒng)中要求提供更多的相關(guān)資料)。
SqlParameterSource
接口的另一個(gè)實(shí)現(xiàn)--BeanPropertySqlParameterSource
為我們提供了更有趣的功能。該類(lèi)包裝一個(gè)類(lèi)似JavaBean的對(duì)象,所需要的命名參數(shù)值將由包裝對(duì)象提供,下面我們使用一個(gè)例子來(lái)更清楚地說(shuō)明它的用法。
// some JavaBean-like class...
public class Actor {
private Long id;
private String firstName;
private String lastName;
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
public Long getId() {
return this.id;
}
// setters omitted...
}
// some JDBC-backed DAO class...
public int countOfActors(Actor exampleActor) {
// notice how the named parameters match the properties of the above 'Actor' class
String sql = "select count(0) from T_ACTOR where first_name = :firstName and last_name = :lastName";
NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(this.getDataSource());
SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);
return template.queryForInt(sql, namedParameters);
}
大家必須牢記一點(diǎn):NamedParameterJdbcTemplate
類(lèi)內(nèi)部包裝了一個(gè)標(biāo)準(zhǔn)的JdbcTemplate
類(lèi)。如果你需要訪問(wèn)其內(nèi)部的JdbcTemplate
實(shí)例(比如訪問(wèn)JdbcTemplate
的一些方法)那么你需要使用getJdbcOperations()
方法返回的JdbcOperations
接口。(JdbcTemplate
實(shí)現(xiàn)了JdbcOperations
接口)。
NamedParameterJdbcTemplate
類(lèi)是線程安全的,該類(lèi)的最佳使用方式不是每次操作的時(shí)候?qū)嵗粋€(gè)新的NamedParameterJdbcTemplate
,而是針對(duì)每個(gè)DataSource
只配置一個(gè)NamedParameterJdbcTemplate
實(shí)例(比如在Spring IoC容器中使用Spring IoC來(lái)進(jìn)行配置),然后在那些使用該類(lèi)的DAO中共享該實(shí)例。
11.2.3. SimpleJdbcTemplate
類(lèi)
注意
請(qǐng)注意該類(lèi)所提供的功能僅適用于Java 5 (Tiger)。
SimpleJdbcTemplate
類(lèi)是JdbcTemplate
類(lèi)的一個(gè)包裝器(wrapper),它利用了Java 5的一些語(yǔ)言特性,比如Varargs和Autoboxing。對(duì)那些用慣了Java 5的程序員,這些新的語(yǔ)言特性還是很好用的。
SimpleJdbcTemplate
類(lèi)利用Java 5的語(yǔ)法特性帶來(lái)的好處可以通過(guò)一個(gè)例子來(lái)說(shuō)明。在下面的代碼片斷中我們首先使用標(biāo)準(zhǔn)的JdbcTemplate
進(jìn)行數(shù)據(jù)訪問(wèn),接下來(lái)使用SimpleJdbcTemplate
做同樣的事情。
// classic JdbcTemplate
-style...
public Actor findActor(long id) {
String sql = "select id, first_name, last_name from T_ACTOR where id = ?";
RowMapper mapper = new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setId(rs.getLong(Long.valueOf(rs.getLong("id"))));
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
};
// normally this would be dependency injected of course...
JdbcTemplate jdbcTemplate = new JdbcTemplate(this.getDataSource());
// notice the cast, and the wrapping up of the 'id' argument
// in an array, and the boxing of the 'id' argument as a reference type
return (Actor) jdbcTemplate.queryForObject(sql, mapper, new Object[] {Long.valueOf(id)});
}
下面是同一方法的另一種實(shí)現(xiàn),惟一不同之處是我們使用了SimpleJdbcTemplate
,這樣代碼顯得更加清晰。
// SimpleJdbcTemplate
-style...
public Actor findActor(long id) {
String sql = "select id, first_name, last_name from T_ACTOR where id = ?";
ParameterizedRowMapper<Actor> mapper = new ParameterizedRowMapper<Actor>() {
// notice the return type with respect to Java 5 covariant return types
public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
Actor actor = new Actor();
actor.setId(rs.getLong("id"));
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
};
// again, normally this would be dependency injected of course...
SimpleJdbcTemplate simpleJdbcTemplate = new SimpleJdbcTemplate(this.getDataSource());
return simpleJdbcTemplate.queryForObject(sql, mapper, id);
}
為了從數(shù)據(jù)庫(kù)中取得數(shù)據(jù),我們首先需要獲取一個(gè)數(shù)據(jù)庫(kù)連接。 Spring通過(guò)DataSource
對(duì)象來(lái)完成這個(gè)工作。 DataSource
是JDBC規(guī)范的一部分, 它被視為一個(gè)通用的數(shù)據(jù)庫(kù)連接工廠。通過(guò)使用DataSource, Container或Framework可以將連接池以及事務(wù)管理的細(xì)節(jié)從應(yīng)用代碼中分離出來(lái)。 作為一個(gè)開(kāi)發(fā)人員,在開(kāi)發(fā)和測(cè)試產(chǎn)品的過(guò)程中,你可能需要知道連接數(shù)據(jù)庫(kù)的細(xì)節(jié)。 但在產(chǎn)品實(shí)施時(shí),你不需要知道這些細(xì)節(jié)。通常數(shù)據(jù)庫(kù)管理員會(huì)幫你設(shè)置好數(shù)據(jù)源。
在使用Spring JDBC時(shí),你既可以通過(guò)JNDI獲得數(shù)據(jù)源,也可以自行配置數(shù)據(jù)源( 使用Spring提供的DataSource實(shí)現(xiàn)類(lèi))。使用后者可以更方便的脫離Web容器來(lái)進(jìn)行單元測(cè)試。 這里我們將使用DriverManagerDataSource
,不過(guò)DataSource有多種實(shí)現(xiàn), 后面我們會(huì)講到。使用DriverManagerDataSource
和你以前獲取一個(gè)JDBC連接 的做法沒(méi)什么兩樣。你首先必須指定JDBC驅(qū)動(dòng)程序的全限定名,這樣DriverManager
才能加載JDBC驅(qū)動(dòng)類(lèi),接著你必須提供一個(gè)url(因JDBC驅(qū)動(dòng)而異,為了保證設(shè)置正確請(qǐng)參考相關(guān)JDBC驅(qū)動(dòng)的文檔), 最后你必須提供一個(gè)用戶連接數(shù)據(jù)庫(kù)的用戶名和密碼。下面我們將通過(guò)一個(gè)例子來(lái)說(shuō)明如何配置一個(gè) DriverManagerDataSource
:
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername("sa");
dataSource.setPassword("");
11.2.5. SQLExceptionTranslator
接口
SQLExceptionTranslator
是一個(gè)接口,如果你需要在 SQLException
和org.springframework.dao.DataAccessException
之間作轉(zhuǎn)換,那么必須實(shí)現(xiàn)該接口。
轉(zhuǎn)換器類(lèi)的實(shí)現(xiàn)可以采用一般通用的做法(比如使用JDBC的SQLState code),如果為了使轉(zhuǎn)換更準(zhǔn)確,也可以進(jìn)行定制(比如使用Oracle的error code)。
SQLErrorCodeSQLExceptionTranslator
是SQLExceptionTranslator的默認(rèn)實(shí)現(xiàn)。 該實(shí)現(xiàn)使用指定數(shù)據(jù)庫(kù)廠商的error code,比采用SQLState
更精確。 轉(zhuǎn)換過(guò)程基于一個(gè)JavaBean(類(lèi)型為SQLErrorCodes
)中的error code。 這個(gè)JavaBean由SQLErrorCodesFactory
工廠類(lèi)創(chuàng)建,其中的內(nèi)容來(lái)自于 "sql-error-codes.xml"配置文件。該文件中的數(shù)據(jù)庫(kù)廠商代碼基于Database MetaData信息中的 DatabaseProductName,從而配合當(dāng)前數(shù)據(jù)庫(kù)的使用。
SQLErrorCodeSQLExceptionTranslator
使用以下的匹配規(guī)則:
-
首先檢查是否存在完成定制轉(zhuǎn)換的子類(lèi)實(shí)現(xiàn)。通常SQLErrorCodeSQLExceptionTranslator
這個(gè)類(lèi)可以作為一個(gè)具體類(lèi)使用,不需要進(jìn)行定制,那么這個(gè)規(guī)則將不適用。
-
接著將SQLException的error code與錯(cuò)誤代碼集中的error code進(jìn)行匹配。 默認(rèn)情況下錯(cuò)誤代碼集將從SQLErrorCodesFactory
取得。 錯(cuò)誤代碼集來(lái)自classpath下的sql-error-codes.xml文件, 它們將與數(shù)據(jù)庫(kù)metadata信息中的database name進(jìn)行映射。
-
如果仍然無(wú)法匹配,最后將調(diào)用fallbackTranslator屬性的translate方法,SQLStateSQLExceptionTranslator
類(lèi)實(shí)例是默認(rèn)的fallbackTranslator。
SQLErrorCodeSQLExceptionTranslator
可以采用下面的方式進(jìn)行擴(kuò)展:
public class MySQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator {
protected DataAccessException customTranslate(String task, String sql, SQLException sqlex) {
if (sqlex.getErrorCode() == -12345) {
return new DeadlockLoserDataAccessException(task, sqlex);
}
return null;
}
}
在上面的這個(gè)例子中,error code為'-12345'
的SQLException 將采用該轉(zhuǎn)換器進(jìn)行轉(zhuǎn)換,而其他的error code將由默認(rèn)的轉(zhuǎn)換器進(jìn)行轉(zhuǎn)換。 為了使用該轉(zhuǎn)換器,必須將其作為參數(shù)傳遞給JdbcTemplate
類(lèi) 的setExceptionTranslator
方法,并在需要使用這個(gè)轉(zhuǎn)換器器的數(shù)據(jù) 存取操作中使用該JdbcTemplate
。 下面的例子演示了如何使用該定制轉(zhuǎn)換器:
// create a JdbcTemplate and set data source
JdbcTemplate jt = new JdbcTemplate();
jt.setDataSource(dataSource);
// create a custom translator and set the DataSource for the default translation lookup
MySQLErrorCodesTransalator tr = new MySQLErrorCodesTransalator();
tr.setDataSource(dataSource);
jt.setExceptionTranslator(tr);
// use the JdbcTemplate for this SqlUpdate
SqlUpdate su = new SqlUpdate();
su.setJdbcTemplate(jt);
su.setSql("update orders set shipping_charge = shipping_charge * 1.05");
su.compile();
su.update();
在上面的定制轉(zhuǎn)換器中,我們給它注入了一個(gè)數(shù)據(jù)源,因?yàn)槲覀內(nèi)匀恍枰?使用默認(rèn)的轉(zhuǎn)換器從sql-error-codes.xml
中獲取錯(cuò)誤代碼集。
我們僅需要非常少的代碼就可以達(dá)到執(zhí)行SQL語(yǔ)句的目的,一旦獲得一個(gè) DataSource
和一個(gè)JdbcTemplate
, 我們就可以使用JdbcTemplate
提供的豐富功能實(shí)現(xiàn)我們的操作。 下面的例子使用了極少的代碼完成創(chuàng)建一張表的工作。
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class ExecuteAStatement {
private JdbcTemplate jt;
private DataSource dataSource;
public void doExecute() {
jt = new JdbcTemplate(dataSource);
jt.execute("create table mytable (id integer, name varchar(100))");
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
除了execute方法之外,JdbcTemplate
還提供了大量的查詢方法。 在這些查詢方法中,有很大一部分是用來(lái)查詢單值的。比如返回一個(gè)匯總(count)結(jié)果 或者從返回行結(jié)果中取得指定列的值。這時(shí)我們可以使用queryForInt(..)
、 queryForLong(..)
或者queryForObject(..)
方法。 queryForObject方法用來(lái)將返回的JDBC類(lèi)型對(duì)象轉(zhuǎn)換成指定的Java對(duì)象,如果類(lèi)型轉(zhuǎn)換失敗將拋出 InvalidDataAccessApiUsageException
異常。 下面的例子演示了兩個(gè)查詢的用法,一個(gè)返回int
值,另一個(gè)返回 String
。
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class RunAQuery {
private JdbcTemplate jt;
private DataSource dataSource;
public int getCount() {
jt = new JdbcTemplate(dataSource);
int count = jt.queryForInt("select count(*) from mytable");
return count;
}
public String getName() {
jt = new JdbcTemplate(dataSource);
String name = (String) jt.queryForObject("select name from mytable", String.class);
return name;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
除了返回單值的查詢方法,JdbcTemplate
還提供了一組返回List結(jié)果 的方法。List中的每一項(xiàng)對(duì)應(yīng)查詢返回結(jié)果中的一行。其中最簡(jiǎn)單的是queryForList
方法, 該方法將返回一個(gè)List
,該List
中的每一條 記錄是一個(gè)Map
對(duì)象,對(duì)應(yīng)應(yīng)數(shù)據(jù)庫(kù)中某一行;而該Map
中的每一項(xiàng)對(duì)應(yīng)該數(shù)據(jù)庫(kù)行中的某一列值。下面的代碼片斷接著上面的例子演示了如何用該方法返回表中 所有記錄:
public List getList() {
jt = new JdbcTemplate(dataSource);
List rows = jt.queryForList("select * from mytable");
return rows;
}
返回的結(jié)果集類(lèi)似下面這種形式:
[{name=Bob, id=1}, {name=Mary, id=2}]
11.2.8. 更新數(shù)據(jù)庫(kù)
JdbcTemplate
還提供了一些更新數(shù)據(jù)庫(kù)的方法。 在下面的例子中,我們根據(jù)給定的主鍵值對(duì)指定的列進(jìn)行更新。 例子中的SQL語(yǔ)句中使用了“?”占位符來(lái)接受參數(shù)(這種做法在更新和查詢SQL語(yǔ)句中很常見(jiàn))。 傳遞的參數(shù)值位于一個(gè)對(duì)象數(shù)組中(基本類(lèi)型需要被包裝成其對(duì)應(yīng)的對(duì)象類(lèi)型)。
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
public class ExecuteAnUpdate {
private JdbcTemplate jt;
private DataSource dataSource;
public void setName(int id, String name) {
jt = new JdbcTemplate(dataSource);
jt.update("update mytable set name = ? where id = ?", new Object[] {name, new Integer(id)});
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
開(kāi)心過(guò)好每一天。。。。。