JdbcTemplate
是core包的核心類。它替我們完成了資源的創建以及釋放工作,從而簡化了我們對JDBC的使用。它還可以幫助我們避免一些常見的錯誤,比如忘記關閉數據庫連接。JdbcTemplate將完成JDBC核心處理流程,比如SQL語句的創建、執行,而把SQL語句的生成以及查詢結果的提取工作留給我們的應用代碼。它可以完成SQL查詢、更新以及調用存儲過程,可以對ResultSet
進行遍歷并加以提取。它還可以捕獲JDBC異常并將其轉換成org.springframework.dao
包中定義的,通用的,信息更豐富的異常。
使用JdbcTemplate進行編碼只需要根據明確定義的一組契約來實現回調接口。PreparedStatementCreator
回調接口通過給定的Connection
創建一個PreparedStatement,包含SQL和任何相關的參數。CallableStatementCreateor
實現同樣的處理,只不過它創建的是CallableStatement。RowCallbackHandler
接口則從數據集的每一行中提取值。
我們可以在一個service實現類中通過傳遞一個DataSource
引用來完成JdbcTemplate的實例化,也可以在application context中配置一個JdbcTemplate bean,來供service使用。需要注意的是DataSource
在application context總是配制成一個bean,第一種情況下,DataSource
bean將傳遞給service,第二種情況下DataSource
bean傳遞給JdbcTemplate bean。因為JdbcTemplate使用回調接口和SQLExceptionTranslator
接口作為參數,所以一般情況下沒有必要通過繼承JdbcTemplate來定義其子類。
JdbcTemplate中使用的所有SQL將會以“DEBUG”級別記入日志(一般情況下日志的category是JdbcTemplate
相應的全限定類名,不過如果需要對JdbcTemplate
進行定制的話,可能是它的子類名)。
11.2.2. NamedParameterJdbcTemplate
類
NamedParameterJdbcTemplate
類增加了在SQL語句中使用命名參數的支持。在此之前,在傳統的SQL語句中,參數都是用'?'
占位符來表示的。 NamedParameterJdbcTemplate
類內部封裝了一個普通的JdbcTemplate
,并作為其代理來完成大部分工作。下面的內容主要針對NamedParameterJdbcTemplate
與JdbcTemplate
的不同之處來加以說明,即如何在SQL語句中使用命名參數。
通過下面的例子我們可以更好地了解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
變量使用了命名參數占位符“first_name”,與其對應的值存在namedParameters
變量中(類型為MapSqlParameterSource
)。
如果你喜歡的話,也可以使用基于Map風格的名值對將命名參數傳遞給NamedParameterJdbcTemplate
(NamedParameterJdbcTemplate
實現了NamedParameterJdbcOperations
接口,剩下的工作將由調用該接口的相應方法來完成,這里我們就不再贅述):
// 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);
}
另外一個值得一提的特性是與NamedParameterJdbcTemplate
位于同一個包中的SqlParameterSource
接口。在前面的代碼片斷中我們已經看到了該接口的實現(即MapSqlParameterSource
類),SqlParameterSource
可以用來作為NamedParameterJdbcTemplate
命名參數的來源。MapSqlParameterSource
類是一個非常簡單的實現,它僅僅是一個java.util.Map
適配器,當然其用法也就不言自明了(如果還有不明了的,可以在Spring的JIRA系統中要求提供更多的相關資料)。
SqlParameterSource
接口的另一個實現--BeanPropertySqlParameterSource
為我們提供了更有趣的功能。該類包裝一個類似JavaBean的對象,所需要的命名參數值將由包裝對象提供,下面我們使用一個例子來更清楚地說明它的用法。
// 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);
}
大家必須牢記一點:NamedParameterJdbcTemplate
類內部包裝了一個標準的JdbcTemplate
類。如果你需要訪問其內部的JdbcTemplate
實例(比如訪問JdbcTemplate
的一些方法)那么你需要使用getJdbcOperations()
方法返回的JdbcOperations
接口。(JdbcTemplate
實現了JdbcOperations
接口)。
NamedParameterJdbcTemplate
類是線程安全的,該類的最佳使用方式不是每次操作的時候實例化一個新的NamedParameterJdbcTemplate
,而是針對每個DataSource
只配置一個NamedParameterJdbcTemplate
實例(比如在Spring IoC容器中使用Spring IoC來進行配置),然后在那些使用該類的DAO中共享該實例。
11.2.3. SimpleJdbcTemplate
類
注意
請注意該類所提供的功能僅適用于Java 5 (Tiger)。
SimpleJdbcTemplate
類是JdbcTemplate
類的一個包裝器(wrapper),它利用了Java 5的一些語言特性,比如Varargs和Autoboxing。對那些用慣了Java 5的程序員,這些新的語言特性還是很好用的。
SimpleJdbcTemplate
類利用Java 5的語法特性帶來的好處可以通過一個例子來說明。在下面的代碼片斷中我們首先使用標準的JdbcTemplate
進行數據訪問,接下來使用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)});
}
下面是同一方法的另一種實現,惟一不同之處是我們使用了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);
}
為了從數據庫中取得數據,我們首先需要獲取一個數據庫連接。 Spring通過DataSource
對象來完成這個工作。 DataSource
是JDBC規范的一部分, 它被視為一個通用的數據庫連接工廠。通過使用DataSource, Container或Framework可以將連接池以及事務管理的細節從應用代碼中分離出來。 作為一個開發人員,在開發和測試產品的過程中,你可能需要知道連接數據庫的細節。 但在產品實施時,你不需要知道這些細節。通常數據庫管理員會幫你設置好數據源。
在使用Spring JDBC時,你既可以通過JNDI獲得數據源,也可以自行配置數據源( 使用Spring提供的DataSource實現類)。使用后者可以更方便的脫離Web容器來進行單元測試。 這里我們將使用DriverManagerDataSource
,不過DataSource有多種實現, 后面我們會講到。使用DriverManagerDataSource
和你以前獲取一個JDBC連接 的做法沒什么兩樣。你首先必須指定JDBC驅動程序的全限定名,這樣DriverManager
才能加載JDBC驅動類,接著你必須提供一個url(因JDBC驅動而異,為了保證設置正確請參考相關JDBC驅動的文檔), 最后你必須提供一個用戶連接數據庫的用戶名和密碼。下面我們將通過一個例子來說明如何配置一個 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
是一個接口,如果你需要在 SQLException
和org.springframework.dao.DataAccessException
之間作轉換,那么必須實現該接口。
轉換器類的實現可以采用一般通用的做法(比如使用JDBC的SQLState code),如果為了使轉換更準確,也可以進行定制(比如使用Oracle的error code)。
SQLErrorCodeSQLExceptionTranslator
是SQLExceptionTranslator的默認實現。 該實現使用指定數據庫廠商的error code,比采用SQLState
更精確。 轉換過程基于一個JavaBean(類型為SQLErrorCodes
)中的error code。 這個JavaBean由SQLErrorCodesFactory
工廠類創建,其中的內容來自于 "sql-error-codes.xml"配置文件。該文件中的數據庫廠商代碼基于Database MetaData信息中的 DatabaseProductName,從而配合當前數據庫的使用。
SQLErrorCodeSQLExceptionTranslator
使用以下的匹配規則:
-
首先檢查是否存在完成定制轉換的子類實現。通常SQLErrorCodeSQLExceptionTranslator
這個類可以作為一個具體類使用,不需要進行定制,那么這個規則將不適用。
-
接著將SQLException的error code與錯誤代碼集中的error code進行匹配。 默認情況下錯誤代碼集將從SQLErrorCodesFactory
取得。 錯誤代碼集來自classpath下的sql-error-codes.xml文件, 它們將與數據庫metadata信息中的database name進行映射。
-
如果仍然無法匹配,最后將調用fallbackTranslator屬性的translate方法,SQLStateSQLExceptionTranslator
類實例是默認的fallbackTranslator。
SQLErrorCodeSQLExceptionTranslator
可以采用下面的方式進行擴展:
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;
}
}
在上面的這個例子中,error code為'-12345'
的SQLException 將采用該轉換器進行轉換,而其他的error code將由默認的轉換器進行轉換。 為了使用該轉換器,必須將其作為參數傳遞給JdbcTemplate
類 的setExceptionTranslator
方法,并在需要使用這個轉換器器的數據 存取操作中使用該JdbcTemplate
。 下面的例子演示了如何使用該定制轉換器:
// 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();
在上面的定制轉換器中,我們給它注入了一個數據源,因為我們仍然需要 使用默認的轉換器從sql-error-codes.xml
中獲取錯誤代碼集。
我們僅需要非常少的代碼就可以達到執行SQL語句的目的,一旦獲得一個 DataSource
和一個JdbcTemplate
, 我們就可以使用JdbcTemplate
提供的豐富功能實現我們的操作。 下面的例子使用了極少的代碼完成創建一張表的工作。
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
還提供了大量的查詢方法。 在這些查詢方法中,有很大一部分是用來查詢單值的。比如返回一個匯總(count)結果 或者從返回行結果中取得指定列的值。這時我們可以使用queryForInt(..)
、 queryForLong(..)
或者queryForObject(..)
方法。 queryForObject方法用來將返回的JDBC類型對象轉換成指定的Java對象,如果類型轉換失敗將拋出 InvalidDataAccessApiUsageException
異常。 下面的例子演示了兩個查詢的用法,一個返回int
值,另一個返回 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結果 的方法。List中的每一項對應查詢返回結果中的一行。其中最簡單的是queryForList
方法, 該方法將返回一個List
,該List
中的每一條 記錄是一個Map
對象,對應應數據庫中某一行;而該Map
中的每一項對應該數據庫行中的某一列值。下面的代碼片斷接著上面的例子演示了如何用該方法返回表中 所有記錄:
public List getList() {
jt = new JdbcTemplate(dataSource);
List rows = jt.queryForList("select * from mytable");
return rows;
}
返回的結果集類似下面這種形式:
[{name=Bob, id=1}, {name=Mary, id=2}]
JdbcTemplate
還提供了一些更新數據庫的方法。 在下面的例子中,我們根據給定的主鍵值對指定的列進行更新。 例子中的SQL語句中使用了“?”占位符來接受參數(這種做法在更新和查詢SQL語句中很常見)。 傳遞的參數值位于一個對象數組中(基本類型需要被包裝成其對應的對象類型)。
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;
}
}
開心過好每一天。。。。。