11.2. 利用JDBC核心類實現(xiàn)JDBC的基本操作和錯誤處理
JdbcTemplate是core包的核心類。它替我們完成了資源的創(chuàng)建以及釋放工作,從而簡化了我們對JDBC的使用。它還可以幫助我們避免一些常見的錯誤,比如忘記關(guān)閉數(shù)據(jù)庫連接。JdbcTemplate將完成JDBC核心處理流程,比如SQL語句的創(chuàng)建、執(zhí)行,而把SQL語句的生成以及查詢結(jié)果的提取工作留給我們的應(yīng)用代碼。它可以完成SQL查詢、更新以及調(diào)用存儲過程,可以對ResultSet進行遍歷并加以提取。它還可以捕獲JDBC異常并將其轉(zhuǎn)換成org.springframework.dao包中定義的,通用的,信息更豐富的異常。
使用JdbcTemplate進行編碼只需要根據(jù)明確定義的一組契約來實現(xiàn)回調(diào)接口。PreparedStatementCreator回調(diào)接口通過給定的Connection創(chuàng)建一個PreparedStatement,包含SQL和任何相關(guān)的參數(shù)。CallableStatementCreateor實現(xiàn)同樣的處理,只不過它創(chuàng)建的是CallableStatement。RowCallbackHandler接口則從數(shù)據(jù)集的每一行中提取值。
我們可以在一個service實現(xiàn)類中通過傳遞一個DataSource引用來完成JdbcTemplate的實例化,也可以在application context中配置一個JdbcTemplate bean,來供service使用。需要注意的是DataSource在application context總是配制成一個bean,第一種情況下,DataSource bean將傳遞給service,第二種情況下DataSource bean傳遞給JdbcTemplate bean。因為JdbcTemplate使用回調(diào)接口和SQLExceptionTranslator接口作為參數(shù),所以一般情況下沒有必要通過繼承JdbcTemplate來定義其子類。
JdbcTemplate中使用的所有SQL將會以“DEBUG”級別記入日志(一般情況下日志的category是JdbcTemplate相應(yīng)的全限定類名,不過如果需要對JdbcTemplate進行定制的話,可能是它的子類名)。
11.2.2. NamedParameterJdbcTemplate類
NamedParameterJdbcTemplate類增加了在SQL語句中使用命名參數(shù)的支持。在此之前,在傳統(tǒng)的SQL語句中,參數(shù)都是用'?'占位符來表示的。 NamedParameterJdbcTemplate類內(nèi)部封裝了一個普通的JdbcTemplate,并作為其代理來完成大部分工作。下面的內(nèi)容主要針對NamedParameterJdbcTemplate與JdbcTemplate的不同之處來加以說明,即如何在SQL語句中使用命名參數(shù)。
通過下面的例子我們可以更好地了解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”,與其對應(yīng)的值存在namedParameters變量中(類型為MapSqlParameterSource)。
如果你喜歡的話,也可以使用基于Map風格的名值對將命名參數(shù)傳遞給NamedParameterJdbcTemplate(NamedParameterJdbcTemplate實現(xiàn)了NamedParameterJdbcOperations接口,剩下的工作將由調(diào)用該接口的相應(yīng)方法來完成,這里我們就不再贅述):
// 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接口。在前面的代碼片斷中我們已經(jīng)看到了該接口的實現(xiàn)(即MapSqlParameterSource類),SqlParameterSource可以用來作為NamedParameterJdbcTemplate命名參數(shù)的來源。MapSqlParameterSource類是一個非常簡單的實現(xiàn),它僅僅是一個java.util.Map適配器,當然其用法也就不言自明了(如果還有不明了的,可以在Spring的JIRA系統(tǒng)中要求提供更多的相關(guān)資料)。
SqlParameterSource接口的另一個實現(xiàn)--BeanPropertySqlParameterSource為我們提供了更有趣的功能。該類包裝一個類似JavaBean的對象,所需要的命名參數(shù)值將由包裝對象提供,下面我們使用一個例子來更清楚地說明它的用法。
// 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類內(nèi)部包裝了一個標準的JdbcTemplate類。如果你需要訪問其內(nèi)部的JdbcTemplate實例(比如訪問JdbcTemplate的一些方法)那么你需要使用getJdbcOperations()方法返回的JdbcOperations接口。(JdbcTemplate實現(xiàn)了JdbcOperations接口)。
NamedParameterJdbcTemplate類是線程安全的,該類的最佳使用方式不是每次操作的時候?qū)嵗粋€新的NamedParameterJdbcTemplate,而是針對每個DataSource只配置一個NamedParameterJdbcTemplate實例(比如在Spring IoC容器中使用Spring IoC來進行配置),然后在那些使用該類的DAO中共享該實例。
11.2.3. SimpleJdbcTemplate類
![[Note]](http://www.redsaga.com/spring_ref/2.0/images/admons/note.png) |
Note |
請注意該類所提供的功能僅適用于Java 5 (Tiger)。
|
SimpleJdbcTemplate類是JdbcTemplate類的一個包裝器(wrapper),它利用了Java 5的一些語言特性,比如Varargs和Autoboxing。對那些用慣了Java 5的程序員,這些新的語言特性還是很好用的。
SimpleJdbcTemplate 類利用Java 5的語法特性帶來的好處可以通過一個例子來說明。在下面的代碼片斷中我們首先使用標準的JdbcTemplate進行數(shù)據(jù)訪問,接下來使用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)});
}
下面是同一方法的另一種實現(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ù)庫中取得數(shù)據(jù),我們首先需要獲取一個數(shù)據(jù)庫連接。 Spring通過DataSource對象來完成這個工作。 DataSource是JDBC規(guī)范的一部分, 它被視為一個通用的數(shù)據(jù)庫連接工廠。通過使用DataSource, Container或Framework可以將連接池以及事務(wù)管理的細節(jié)從應(yīng)用代碼中分離出來。 作為一個開發(fā)人員,在開發(fā)和測試產(chǎn)品的過程中,你可能需要知道連接數(shù)據(jù)庫的細節(jié)。 但在產(chǎn)品實施時,你不需要知道這些細節(jié)。通常數(shù)據(jù)庫管理員會幫你設(shè)置好數(shù)據(jù)源。
在使用Spring JDBC時,你既可以通過JNDI獲得數(shù)據(jù)源,也可以自行配置數(shù)據(jù)源( 使用Spring提供的DataSource實現(xiàn)類)。使用后者可以更方便的脫離Web容器來進行單元測試。 這里我們將使用DriverManagerDataSource,不過DataSource有多種實現(xiàn), 后面我們會講到。使用DriverManagerDataSource和你以前獲取一個JDBC連接 的做法沒什么兩樣。你首先必須指定JDBC驅(qū)動程序的全限定名,這樣DriverManager 才能加載JDBC驅(qū)動類,接著你必須提供一個url(因JDBC驅(qū)動而異,為了保證設(shè)置正確請參考相關(guān)JDBC驅(qū)動的文檔), 最后你必須提供一個用戶連接數(shù)據(jù)庫的用戶名和密碼。下面我們將通過一個例子來說明如何配置一個 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之間作轉(zhuǎn)換,那么必須實現(xiàn)該接口。
轉(zhuǎn)換器類的實現(xiàn)可以采用一般通用的做法(比如使用JDBC的SQLState code),如果為了使轉(zhuǎn)換更準確,也可以進行定制(比如使用Oracle的error code)。
SQLErrorCodeSQLExceptionTranslator是SQLExceptionTranslator的默認實現(xiàn)。 該實現(xiàn)使用指定數(shù)據(jù)庫廠商的error code,比采用SQLState更精確。 轉(zhuǎn)換過程基于一個JavaBean(類型為SQLErrorCodes)中的error code。 這個JavaBean由SQLErrorCodesFactory工廠類創(chuàng)建,其中的內(nèi)容來自于 "sql-error-codes.xml"配置文件。該文件中的數(shù)據(jù)庫廠商代碼基于Database MetaData信息中的 DatabaseProductName,從而配合當前數(shù)據(jù)庫的使用。
SQLErrorCodeSQLExceptionTranslator使用以下的匹配規(guī)則:
-
首先檢查是否存在完成定制轉(zhuǎn)換的子類實現(xiàn)。通常SQLErrorCodeSQLExceptionTranslator 這個類可以作為一個具體類使用,不需要進行定制,那么這個規(guī)則將不適用。
-
接著將SQLException的error code與錯誤代碼集中的error code進行匹配。 默認情況下錯誤代碼集將從SQLErrorCodesFactory取得。 錯誤代碼集來自classpath下的sql-error-codes.xml文件, 它們將與數(shù)據(jù)庫metadata信息中的database name進行映射。
-
如果仍然無法匹配,最后將調(diào)用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 將采用該轉(zhuǎn)換器進行轉(zhuǎn)換,而其他的error code將由默認的轉(zhuǎn)換器進行轉(zhuǎn)換。 為了使用該轉(zhuǎn)換器,必須將其作為參數(shù)傳遞給JdbcTemplate類 的setExceptionTranslator方法,并在需要使用這個轉(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)換器中,我們給它注入了一個數(shù)據(jù)源,因為我們?nèi)匀恍枰?使用默認的轉(zhuǎn)換器從sql-error-codes.xml中獲取錯誤代碼集。
我們僅需要非常少的代碼就可以達到執(zhí)行SQL語句的目的,一旦獲得一個 DataSource和一個JdbcTemplate, 我們就可以使用JdbcTemplate提供的豐富功能實現(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還提供了大量的查詢方法。 在這些查詢方法中,有很大一部分是用來查詢單值的。比如返回一個匯總(count)結(jié)果 或者從返回行結(jié)果中取得指定列的值。這時我們可以使用queryForInt(..)、 queryForLong(..)或者queryForObject(..)方法。 queryForObject方法用來將返回的JDBC類型對象轉(zhuǎn)換成指定的Java對象,如果類型轉(zhuǎn)換失敗將拋出 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結(jié)果 的方法。List中的每一項對應(yīng)查詢返回結(jié)果中的一行。其中最簡單的是queryForList方法, 該方法將返回一個List,該List中的每一條 記錄是一個Map對象,對應(yīng)應(yīng)數(shù)據(jù)庫中某一行;而該Map 中的每一項對應(yīng)該數(shù)據(jù)庫行中的某一列值。下面的代碼片斷接著上面的例子演示了如何用該方法返回表中 所有記錄:
public List getList() {
jt = new JdbcTemplate(dataSource);
List rows = jt.queryForList("select * from mytable");
return rows;
}
返回的結(jié)果集類似下面這種形式:
[{name=Bob, id=1}, {name=Mary, id=2}]
JdbcTemplate還提供了一些更新數(shù)據(jù)庫的方法。 在下面的例子中,我們根據(jù)給定的主鍵值對指定的列進行更新。 例子中的SQL語句中使用了“?”占位符來接受參數(shù)(這種做法在更新和查詢SQL語句中很常見)。 傳遞的參數(shù)值位于一個對象數(shù)組中(基本類型需要被包裝成其對應(yīng)的對象類型)。
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;
}
}
DataSourceUtils作為一個幫助類提供易用且強大的數(shù)據(jù)庫訪問能力, 我們可以使用該類提供的靜態(tài)方法從JNDI獲取數(shù)據(jù)庫連接以及在必要的時候關(guān)閉之。 它提供支持線程綁定的數(shù)據(jù)庫連接(比如使用DataSourceTransactionManager 的時候,將把數(shù)據(jù)庫連接綁定到當前的線程上)。
注:getDataSourceFromJndi(..)方法主要用于那些沒有使用bean factory 或者application context的場合。如果使用application context,那么最好是在 JndiObjectFactoryBean中配置bean或者直接使用 JdbcTemplate實例。JndiObjectFactoryBean 能夠通過JNDI獲取DataSource并將 DataSource作為引用參數(shù)傳遞給其他bean。 這樣,在不同的DataSource之間切換只需要修改配置文件即可, 甚至我們可以用一個非JNDI的DataSource來替換 FactoryBean定義!
11.3.2. SmartDataSource接口
SmartDataSource是DataSource 接口的一個擴展,用來提供數(shù)據(jù)庫連接。使用該接口的類在指定的操作之后可以檢查是否需要關(guān)閉連接。 該接口在某些情況下非常有用,比如有些情況需要重用數(shù)據(jù)庫連接。
11.3.3. AbstractDataSource類
AbstractDataSource是一個實現(xiàn)了DataSource 接口的abstract基類。它實現(xiàn)了DataSource接口的 一些無關(guān)痛癢的方法,如果你需要實現(xiàn)自己的DataSource,那么繼承 該類是個好主意。
11.3.4. SingleConnectionDataSource類
SingleConnectionDataSource是SmartDataSource接口 的一個實現(xiàn),其內(nèi)部包裝了一個單連接。該連接在使用之后將不會關(guān)閉,很顯然它不能在多線程 的環(huán)境下使用。
當客戶端代碼調(diào)用close方法的時候,如果它總是假設(shè)數(shù)據(jù)庫連接來自連接池(就像使用持久化工具時一樣), 你應(yīng)該將suppressClose設(shè)置為true。 這樣,通過該類獲取的將是代理連接(禁止關(guān)閉)而不是原有的物理連接。 需要注意的是,我們不能把使用該類獲取的數(shù)據(jù)庫連接造型(cast)為Oracle Connection之類的本地數(shù)據(jù)庫連接。
SingleConnectionDataSource主要在測試的時候使用。 它使得測試代碼很容易脫離應(yīng)用服務(wù)器而在一個簡單的JNDI環(huán)境下運行。 與DriverManagerDataSource不同的是,它始終只會使用同一個數(shù)據(jù)庫連接, 從而避免每次建立物理連接的開銷。
11.3.5. DriverManagerDataSource類
DriverManagerDataSource類實現(xiàn)了 SmartDataSource接口。在applicationContext.xml中可以使用 bean properties來設(shè)置JDBC Driver屬性,該類每次返回的都是一個新的連接。
該類主要在測試以及脫離J2EE容器的獨立環(huán)境中使用。它既可以用來在application context中作為一個 DataSource bean,也可以在簡單的JNDI環(huán)境下使用。 由于Connection.close()僅僅只是簡單的關(guān)閉數(shù)據(jù)庫連接,因此任何能夠獲取 DataSource的持久化代碼都能很好的工作。不過使用JavaBean風格的連接池 (比如commons-dbcp)也并非難事。即使是在測試環(huán)境下,使用連接池也是一種比使用 DriverManagerDataSource更好的做法。
11.3.6. TransactionAwareDataSourceProxy類
TransactionAwareDataSourceProxy作為目標DataSource的一個代理, 在對目標DataSource包裝的同時,還增加了Spring的事務(wù)管理能力, 在這一點上,這個類的功能非常像J2EE服務(wù)器所提供的事務(wù)化的JNDI DataSource。
![[Note]](http://www.redsaga.com/spring_ref/2.0/images/admons/note.png) |
Note |
該類幾乎很少被用到,除非現(xiàn)有代碼在被調(diào)用的時候需要一個標準的 JDBC DataSource接口實現(xiàn)作為參數(shù)。 這種情況下,這個類可以使現(xiàn)有代碼參與Spring的事務(wù)管理。通常最好的做法是使用更高層的抽象 來對數(shù)據(jù)源進行管理,比如JdbcTemplate和DataSourceUtils等等。
|
如果需要更詳細的資料,請參考TransactionAwareDataSourceProxy JavaDoc 。
11.3.7. DataSourceTransactionManager類
DataSourceTransactionManager類是 PlatformTransactionManager接口的一個實現(xiàn),用于處理單JDBC數(shù)據(jù)源。 它將從指定DataSource取得的JDBC連接綁定到當前線程,因此它也支持了每個數(shù)據(jù)源對應(yīng)到一個線程。
我們推薦在應(yīng)用代碼中使用DataSourceUtils.getConnection(DataSource)來獲取 JDBC連接,而不是使用J2EE標準的DataSource.getConnection。因為前者將拋出 unchecked的org.springframework.dao異常,而不是checked的 SQLException異常。Spring Framework中所有的類(比如 JdbcTemplate)都采用這種做法。如果不需要和這個 DataSourceTransactionManager類一起使用,DataSourceUtils 提供的功能跟一般的數(shù)據(jù)庫連接策略沒有什么兩樣,因此它可以在任何場景下使用。
DataSourceTransactionManager類支持定制隔離級別,以及對SQL語句查詢超時的設(shè)定。 為了支持后者,應(yīng)用代碼必須使用JdbcTemplate或者在每次創(chuàng)建SQL語句時調(diào)用 DataSourceUtils.applyTransactionTimeout方法。
在使用單個數(shù)據(jù)源的情形下,你可以用DataSourceTransactionManager來替代JtaTransactionManager, 因為DataSourceTransactionManager不需要容器支持JTA。如果你使用DataSourceUtils.getConnection(DataSource)來獲取 JDBC連接,二者之間的切換只需要更改一些配置。最后需要注意的一點就是JtaTransactionManager不支持隔離級別的定制!
org.springframework.jdbc.object包下的類允許用戶以更加 面向?qū)ο蟮姆绞饺ピL問數(shù)據(jù)庫。比如說,用戶可以執(zhí)行查詢并返回一個list, 該list作為一個結(jié)果集將把從數(shù)據(jù)庫中取出的列數(shù)據(jù)映射到業(yè)務(wù)對象的屬性上。 用戶也可以執(zhí)行存儲過程,以及運行更新、刪除以及插入SQL語句。
![[Note]](http://www.redsaga.com/spring_ref/2.0/images/admons/note.png) |
Note |
在許多Spring開發(fā)人員中間存在有一種觀點,那就是下面將要提到的各種RDBMS操作類 (StoredProcedure類除外) 通常也可以直接使用JdbcTemplate相關(guān)的方法來替換。 相對于把一個查詢操作封裝成一個類而言,直接調(diào)用JdbcTemplate方法將更簡單 而且更容易理解。
必須說明的一點就是,這僅僅只是一種觀點而已, 如果你認為你可以從直接使用RDBMS操作類中獲取一些額外的好處, 你不妨根據(jù)自己的需要和喜好進行不同的選擇。
|
SqlQuery是一個可重用、線程安全的類,它封裝了一個SQL查詢。 其子類必須實現(xiàn)newResultReader()方法,該方法用來在遍歷 ResultSet的時候能使用一個類來保存結(jié)果。 我們很少需要直接使用SqlQuery,因為其子類 MappingSqlQuery作為一個更加易用的實現(xiàn)能夠?qū)⒔Y(jié)果集中的行映射為Java對象。 SqlQuery還有另外兩個擴展分別是 MappingSqlQueryWithParameters和UpdatableSqlQuery。
MappingSqlQuery是一個可重用的查詢抽象類,其具體類必須實現(xiàn) mapRow(ResultSet, int)抽象方法來將結(jié)果集中的每一行轉(zhuǎn)換成Java對象。
在SqlQuery的各種實現(xiàn)中, MappingSqlQuery是最常用也是最容易使用的一個。
下面這個例子演示了一個定制查詢,它將從客戶表中取得的數(shù)據(jù)映射到一個 Customer類實例。
private class CustomerMappingQuery extends MappingSqlQuery {
public CustomerMappingQuery(DataSource ds) {
super(ds, "SELECT id, name FROM customer WHERE id = ?");
super.declareParameter(new SqlParameter("id", Types.INTEGER));
compile();
}
public Object mapRow(ResultSet rs, int rowNumber) throws SQLException {
Customer cust = new Customer();
cust.setId((Integer) rs.getObject("id"));
cust.setName(rs.getString("name"));
return cust;
}
}
在上面的例子中,我們?yōu)橛脩舨樵兲峁┝艘粋€構(gòu)造函數(shù)并為構(gòu)造函數(shù)傳遞了一個 DataSource參數(shù)。在構(gòu)造函數(shù)里面我們把 DataSource和一個用來返回查詢結(jié)果的SQL語句作為參數(shù) 調(diào)用父類的構(gòu)造函數(shù)。SQL語句將被用于生成一個PreparedStatement對象, 因此它可以包含占位符來傳遞參數(shù)。而每一個SQL語句的參數(shù)必須通過調(diào)用 declareParameter方法來進行聲明,該方法需要一個 SqlParameter(封裝了一個字段名字和一個 java.sql.Types中定義的JDBC類型)對象作為參數(shù)。 所有參數(shù)定義完之后,我們調(diào)用compile()方法來對SQL語句進行預(yù)編譯。
下面讓我們看看該定制查詢初始化并執(zhí)行的代碼:
public Customer getCustomer(Integer id) {
CustomerMappingQuery custQry = new CustomerMappingQuery(dataSource);
Object[] parms = new Object[1];
parms[0] = id;
List customers = custQry.execute(parms);
if (customers.size() > 0) {
return (Customer) customers.get(0);
}
else {
return null;
}
}
在上面的例子中,getCustomer方法通過傳遞惟一參數(shù)id來返回一個客戶對象。 該方法內(nèi)部在創(chuàng)建CustomerMappingQuery實例之后, 我們創(chuàng)建了一個對象數(shù)組用來包含要傳遞的查詢參數(shù)。這里我們只有唯一的一個 Integer參數(shù)。執(zhí)行CustomerMappingQuery的 execute方法之后,我們得到了一個List,該List中包含一個 Customer對象,如果有對象滿足查詢條件的話。
SqlUpdate類封裝了一個可重復(fù)使用的SQL更新操作。 跟所有RdbmsOperation類一樣,SqlUpdate可以在SQL中定義參數(shù)。
該類提供了一系列update()方法,就像SqlQuery提供的一系列execute()方法一樣。
SqlUpdate是一個具體的類。通過在SQL語句中定義參數(shù),這個類可以支持 不同的更新方法,我們一般不需要通過繼承來實現(xiàn)定制。
import java.sql.Types;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.SqlUpdate;
public class UpdateCreditRating extends SqlUpdate {
public UpdateCreditRating(DataSource ds) {
setDataSource(ds);
setSql("update customer set credit_rating = ? where id = ?");
declareParameter(new SqlParameter(Types.NUMERIC));
declareParameter(new SqlParameter(Types.NUMERIC));
compile();
}
/**
* @param id for the Customer to be updated
* @param rating the new value for credit rating
* @return number of rows updated
*/
public int run(int id, int rating) {
Object[] params =
new Object[] {
new Integer(rating),
new Integer(id)};
return update(params);
}
}
StoredProcedure類是一個抽象基類,它是對RDBMS存儲過程的一種抽象。 該類提供了多種execute(..)方法,不過這些方法的訪問類型都是protected的。
從父類繼承的sql屬性用來指定RDBMS存儲過程的名字。 盡管該類提供了許多必須在JDBC3.0下使用的功能,但是我們更關(guān)注的是JDBC 3.0中引入的命名參數(shù)特性。
下面的程序演示了如何調(diào)用Oracle中的sysdate()函數(shù)。 這里我們創(chuàng)建了一個繼承StoredProcedure的子類,雖然它沒有輸入?yún)?shù), 但是我必須通過使用SqlOutParameter來聲明一個日期類型的輸出參數(shù)。 execute()方法將返回一個map,map中的每個entry是一個用參數(shù)名作key, 以輸出參數(shù)為value的名值對。
import java.sql.Types;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.datasource.*;
import org.springframework.jdbc.object.StoredProcedure;
public class TestStoredProcedure {
public static void main(String[] args) {
TestStoredProcedure t = new TestStoredProcedure();
t.test();
System.out.println("Done!");
}
void test() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("oracle.jdbc.OracleDriver");
ds.setUrl("jdbc:oracle:thin:@localhost:1521:mydb");
ds.setUsername("scott");
ds.setPassword("tiger");
MyStoredProcedure sproc = new MyStoredProcedure(ds);
Map results = sproc.execute();
printMap(results);
}
private class MyStoredProcedure extends StoredProcedure {
private static final String SQL = "sysdate";
public MyStoredProcedure(DataSource ds) {
setDataSource(ds);
setFunction(true);
setSql(SQL);
declareParameter(new SqlOutParameter("date", Types.DATE));
compile();
}
public Map execute() {
// the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
return execute(new HashMap());
}
}
private static void printMap(Map results) {
for (Iterator it = results.entrySet().iterator(); it.hasNext(); ) {
System.out.println(it.next());
}
}
}
下面是StoredProcedure的另一個例子,它使用了兩個Oracle游標類型的輸出參數(shù)。
import oracle.jdbc.driver.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
public class TitlesAndGenresStoredProcedure extends StoredProcedure {
private static final String SPROC_NAME = "AllTitlesAndGenres";
public TitlesAndGenresStoredProcedure(DataSource dataSource) {
super(dataSource, SPROC_NAME);
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
declareParameter(new SqlOutParameter("genres", OracleTypes.CURSOR, new GenreMapper()));
compile();
}
public Map execute() {
// again, this sproc has no input parameters, so an empty Map is supplied...
return super.execute(new HashMap());
}
}
值得注意的是TitlesAndGenresStoredProcedure構(gòu)造函數(shù)中 declareParameter(..)的SqlOutParameter參數(shù), 該參數(shù)使用了RowMapper接口的實現(xiàn)。 這是一種非常方便而強大的重用方式。 下面我們來看一下RowMapper的兩個具體實現(xiàn)。
首先是TitleMapper類,它簡單的把ResultSet中的每一行映射為一個Title Domain Object。
import com.foo.sprocs.domain.Title;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
public final class TitleMapper implements RowMapper {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Title title = new Title();
title.setId(rs.getLong("id"));
title.setName(rs.getString("name"));
return title;
}
}
另一個是GenreMapper類,也是非常簡單的將ResultSet中的每一行映射為一個Genre Domain Object。
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Genre;
public final class GenreMapper implements RowMapper {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Genre(rs.getString("name"));
}
}
如果你需要給存儲過程傳輸入?yún)?shù)(這些輸入?yún)?shù)是在RDBMS存儲過程中定義好了的), 則需要提供一個指定類型的execute(..)方法, 該方法將調(diào)用基類的protected execute(Map parameters)方法。 例如:
import oracle.jdbc.driver.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
public class TitlesAfterDateStoredProcedure extends StoredProcedure {
private static final String SPROC_NAME = "TitlesAfterDate";
private static final String CUTOFF_DATE_PARAM = "cutoffDate";
public TitlesAfterDateStoredProcedure(DataSource dataSource) {
super(dataSource, SPROC_NAME);
declaraParameter(new SqlParameter(CUTOFF_DATE_PARAM, Types.DATE);
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
compile();
}
public Map execute(Date cutoffDate) {
Map inputs = new HashMap();
inputs.put(CUTOFF_DATE_PARAM, cutoffDate);
return super.execute(inputs);
}
}
SqlFunction RDBMS操作類封裝了一個SQL“函數(shù)”包裝器(wrapper), 該包裝器適用于查詢并返回一個單行結(jié)果集。默認返回的是一個int值, 不過我們可以采用類似JdbcTemplate中的queryForXxx 做法自己實現(xiàn)來返回其它類型。SqlFunction優(yōu)勢在于我們不必創(chuàng)建 JdbcTemplate,這些它都在內(nèi)部替我們做了。
該類的主要用途是調(diào)用SQL函數(shù)來返回一個單值的結(jié)果集,比如類似“select user()”、 “select sysdate from dual”的查詢。如果需要調(diào)用更復(fù)雜的存儲函數(shù), 可以使用StoredProcedure或SqlCall。
SqlFunction是一個具體類,通常我們不需要它的子類。 其用法是創(chuàng)建該類的實例,然后聲明SQL語句以及參數(shù)就可以調(diào)用相關(guān)的run方法去多次執(zhí)行函數(shù)。 下面的例子用來返回指定表的記錄行數(shù):
public int countRows() {
SqlFunction sf = new SqlFunction(dataSource, "select count(*) from mytable");
sf.compile();
return sf.run();
}