javascript
使用Spring JDBC进行数据访问 (JdbcTemplate/NamedParameterJdbcTemplate/SimpleJdbcTemplate/SimpleJdbcCall/Stor)
http://www.cnblogs.com/webcc/archive/2012/04/11/2442680.html
使用Spring JDBC進行數據訪問
11.1.?簡介
Spring JDBC抽象框架所帶來的價值將在以下幾個方面得以體現:(注:使用了Spring JDBC抽象框架之后,應用開發人員只需要完成斜體字部分的編碼工作。)
定義數據庫連接參數
打開數據庫連接
聲明SQL語句
預編譯并執行SQL語句
遍歷查詢結果(如果需要的話)
處理每一次遍歷操作
處理拋出的任何異常
處理事務
關閉數據庫連接
Spring將替我們完成所有使用JDBC API進行開發的單調乏味的、底層細節處理工作。
11.1.1.?選擇一種工作模式
使用Spring進行基本的JDBC訪問數據庫有多種選擇。Spring至少提供了三種不同的工作模式:JdbcTemplate, 一個在Spring2.5中新提供的SimpleJdbc類能夠更好的處理數據庫元數據; 還有一種稱之為RDBMS Object的風格的面向對象封裝方式, 有點類似于JDO的查詢設計。 我們在這里簡要列舉你采取某一種工作方式的主要理由. 不過請注意, 即使你選擇了其中的一種工作模式, 你依然可以在你的代碼中混用其他任何一種模式以獲取其帶來的好處和優勢。 所有的工作模式都必須要求JDBC 2.0以上的數據庫驅動的支持, 其中一些高級的功能可能需要JDBC 3.0以上的數據庫驅動支持。
-
JdbcTemplate - 這是經典的也是最常用的Spring對于JDBC訪問的方案。這也是最低級別的封裝, 其他的工作模式事實上在底層使用了JdbcTemplate作為其底層的實現基礎。JdbcTemplate在JDK 1.4以上的環境上工作得很好。
-
NamedParameterJdbcTemplate - 對JdbcTemplate做了封裝,提供了更加便捷的基于命名參數的使用方式而不是傳統的JDBC所使用的“?”作為參數的占位符。這種方式在你需要為某個SQL指定許多個參數時,顯得更加直觀而易用。該特性必須工作在JDK 1.4以上。
-
SimpleJdbcTemplate - 這個類結合了JdbcTemplate和NamedParameterJdbcTemplate的最常用的功能,同時它也利用了一些Java 5的特性所帶來的優勢,例如泛型、varargs和autoboxing等,從而提供了更加簡便的API訪問方式。需要工作在Java 5以上的環境中。
-
SimpleJdbcInsert 和 SimpleJdbcCall - 這兩個類可以充分利用數據庫元數據的特性來簡化配置。通過使用這兩個類進行編程,你可以僅僅提供數據庫表名或者存儲過程的名稱以及一個Map作為參數。其中Map的key需要與數據庫表中的字段保持一致。這兩個類通常和SimpleJdbcTemplate配合使用。這兩個類需要工作在JDK 5以上,同時數據庫需要提供足夠的元數據信息。
-
RDBMS 對象包括MappingSqlQuery, SqlUpdate and StoredProcedure - 這種方式允許你在初始化你的數據訪問層時創建可重用并且線程安全的對象。該對象在你定義了你的查詢語句,聲明查詢參數并編譯相應的Query之后被模型化。一旦模型化完成,任何執行函數就可以傳入不同的參數對之進行多次調用。這種方式需要工作在JDK 1.4以上。
11.1.2.?Spring JDBC包結構
Spring Framework的JDBC抽象框架由四個包構成:core、 dataSource、object以及support。
org.springframework.jdbc.core包由JdbcTemplate類以及相關的回調接口(callback interface)和類組成。 org.springframework.jdbc.core.simple 子包則包含了 SimpleJdbcTemplate 類以及相關的SimpleJdbcInsert 類和SimpleJdbcCall 類。 org.springframework.jdbc.core.namedparam 子包則包含了NamedParameterJdbcTemplate 類以及其相關的支持類。
org.springframework.jdbc.datasource包提供了一些工具類來簡化對DataSource的訪問。同時提供了多種簡單的DataSource實現。這些實現可以脫離J2EE容器進行獨立測試和運行。 這些工具類提供了一些靜態方法,允許你通過JNDI來獲取數據庫連接和關閉連接。同時支持綁定到當前線程的數據庫連接。例如使用DataSourceTransactionManager。
接著org.springframework.jdbc.object包包含了一些類,用于將RDBMS查詢、更新以及存儲過程表述為一些可重用的、線程安全的對象。這種方式通過JDO進行模型化,不過這些通過查詢返回的對象是與數據庫脫離的對象。 這種對于JDBC的高層次的封裝是基于org.springframework.jdbc.core包對JDBC的低層次封裝之上的。
最后,org.springframework.jdbc.support包定義了SQLException轉化類以及一些其他的工具類。
在JDBC調用過程中所拋出的異常都會被轉化為在org.springframework.dao包中定義的異常。也就是說,凡是使用Spring的JDBC封裝層的代碼無需實現任何JDBC或者RDBMS相關的異常處理。所有的這些被轉化的異常都是unchecked異常,因而也給了你一種額外的選擇,你可以抓住這些異常,從而轉化成其他類型的異常被允許調用者傳播。
11.2.?利用JDBC核心類控制JDBC的基本操作和錯誤處理
11.2.1.?JdbcTemplate類
JdbcTemplate是core包的核心類。它替我們完成了資源的創建以及釋放工作,從而簡化了我們對JDBC的使用。 它還可以幫助我們避免一些常見的錯誤,比如忘記關閉數據庫連接。 JdbcTemplate將完成JDBC核心處理流程,比如SQL語句的創建、執行,而把SQL語句的生成以及查詢結果的提取工作留給我們的應用代碼。 它可以完成SQL查詢、更新以及調用存儲過程,可以對ResultSet進行遍歷并加以提取。 它還可以捕獲JDBC異常并將其轉換成org.springframework.dao包中定義的,通用的,信息更豐富的異常。
使用JdbcTemplate進行編碼只需要根據明確定義的一組契約來實現回調接口。 PreparedStatementCreator回調接口通過給定的Connection創建一個PreparedStatement,包含SQL和任何相關的參數。 CallableStatementCreateor實現同樣的處理,只不過它創建的是CallableStatement。 RowCallbackHandler接口則從數據集的每一行中提取值。
我們可以在DAO實現類中通過傳遞一個DataSource引用來完成JdbcTemplate的實例化,也可以在Spring的IoC容器中配置一個JdbcTemplate的bean并賦予DAO實現類作為一個實例。 需要注意的是DataSource在Spring的IoC容器中總是配制成一個bean,第一種情況下,DataSource bean將傳遞給service,第二種情況下DataSource bean傳遞給JdbcTemplate bean。
最后,JdbcTemplate中使用的所有SQL將會以“DEBUG”級別記入日志(一般情況下日志的category是JdbcTemplate相應的全限定類名,不過如果需要對JdbcTemplate進行定制的話,可能是它的子類名)。
11.2.1.1.?一些示例
下面是一些使用JdbcTemplate類的示例。(這些示例并不是完整展示所有的JdbcTemplate所暴露出來的功能。請查看與之相關的Javadoc)。
11.2.1.1.1.?查詢(SELECT)
一個簡單的例子用于展示如何獲取一個表中的所有行數。
int rowCount = this.jdbcTemplate.queryForInt("select count(0) from t_accrual");一個簡單的例子展示如何進行參數綁定。
int countOfActorsNamedJoe = this.jdbcTemplate.queryForInt("select count(0) from t_actors where first_name = ?", new Object[]{"Joe"});查詢一個String。
String surname = (String) this.jdbcTemplate.queryForObject("select surname from t_actor where id = ?", new Object[]{new Long(1212)}, String.class);查詢并將結果記錄為一個簡單的數據模型。
Actor actor = (Actor) this.jdbcTemplate.queryForObject("select first_name, surname from t_actor where id = ?",new Object[]{new Long(1212)},new RowMapper() {public Object mapRow(ResultSet rs, int rowNum) throws SQLException {Actor actor = new Actor();actor.setFirstName(rs.getString("first_name"));actor.setSurname(rs.getString("surname"));return actor;}});查詢并組裝多個數據模型。
Collection actors = this.jdbcTemplate.query("select first_name, surname from t_actor",new RowMapper() {public Object mapRow(ResultSet rs, int rowNum) throws SQLException {Actor actor = new Actor();actor.setFirstName(rs.getString("first_name"));actor.setSurname(rs.getString("surname"));return actor;}});如果最后2個示例中的代碼出現在同一段程序中,我們有必要去掉這些重復的RowMapper匿名類代碼,將這些代碼抽取到一個單獨的類中(通常是一個靜態的內部類)。 這樣,這個內部類就可以在DAO的方法中被共享。因而,最后2個示例寫成如下的形式將更加好:
public Collection findAllActors() {return this.jdbcTemplate.query( "select first_name, surname from t_actor", new ActorMapper()); }private static final class ActorMapper implements RowMapper {public Object mapRow(ResultSet rs, int rowNum) throws SQLException {Actor actor = new Actor();actor.setFirstName(rs.getString("first_name"));actor.setSurname(rs.getString("surname"));return actor;} }11.2.1.1.2.?更新(INSERT/UPDATE/DELETE)
this.jdbcTemplate.update("insert into t_actor (first_name, surname) values (?, ?)", new Object[] {"Leonor", "Watling"}); this.jdbcTemplate.update("update t_actor set weapon = ? where id = ?", new Object[] {"Banjo", new Long(5276)}); this.jdbcTemplate.update("delete from actor where id = ?",new Object[] {new Long.valueOf(actorId)});11.2.1.1.3.?其他操作
execute(..)方法可以被用作執行任何類型的SQL,甚至是DDL語句。 這個方法的實現需要傳入一個回調接口、需要綁定的參數數組等作為參數。
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");調用一個簡單的存儲過程(更多復雜的存儲過程支持請參見存儲過程支持)。
this.jdbcTemplate.update("call SUPPORT.REFRESH_ACTORS_SUMMARY(?)", new Object[]{Long.valueOf(unionId)});11.2.1.2.?JdbcTemplate 的最佳實踐
JdbcTemplate類的實例是線程安全的實例。這一點非常重要,正因為如此,你可以配置一個簡單的JdbcTemplate實例,并將這個“共享的”、“安全的”實例注入到不同的DAO類中去。 另外, JdbcTemplate 是有狀態的,因為他所維護的DataSource 實例是有狀態的,但是這種狀態是無法變化的。
使用JdbcTemplate的一個常見的最佳實踐(同時也是SimpleJdbcTemplate和NamedParameterJdbcTemplate 類的最佳實踐)就是在Spring配置文件中配置一個DataSource實例,然后將這個共享的DataSource實例助于到你的DAO中去。 而JdbcTemplate的實例將在DataSource的setter方法中被創建。這樣的話,DAO可能看上去像這樣:
public class JdbcCorporateEventDao implements CorporateEventDao {private JdbcTemplate jdbcTemplate;public void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);}// JDBC-backed implementations of the methods on the CorporateEventDao follow... }相關的配置看上去就像這樣。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"><bean id="corporateEventDao" class="com.example.JdbcCorporateEventDao"><property name="dataSource" ref="dataSource"/></bean><!-- the DataSource (parameterized for configuration via a PropertyPlaceHolderConfigurer) --><bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean></beans>如果你使用Spring提供的JdbcDaoSupport類,并且你的那些基于JDBC的DAO都繼承自這個類,那么你會自動地從JdbcDaoSupport類中繼承了setDataSource(..)方法。 是否將你的DAO類繼承自這些類完全取決于你自己的決定,事實上這并不是必須的,如果你看一下JdbcDaoSupport類你會發現,這里只是提供了一個簡便的方式而已。
無論你是否使用上述這種初始化方式,都無需在執行某些SQL操作時多次創建一個JdbcTemplate實例。記住,一旦JdbcTemplate被創建,他是一個線程安全的對象。 一個你需要創建多次JdbcTemplate實例的理由可能在于,你的應用需要訪問多個不同的數據庫,從而需要不同的DataSources來創建不同的JdbcTemplates實例。
11.2.2.?NamedParameterJdbcTemplate類
NamedParameterJdbcTemplate類為JDBC操作增加了命名參數的特性支持,而不是傳統的使用('?')作為參數的占位符。NamedParameterJdbcTemplate類對JdbcTemplate類進行了封裝, 在底層,JdbcTemplate完成了多數的工作。這一個章節將主要描述NamedParameterJdbcTemplate類與JdbcTemplate類的一些區別,也就是使用命名參數進行JDBC操作。
// some JDBC-backed DAO class... private NamedParameterJdbcTemplate namedParameterJdbcTemplate;public void setDataSource(DataSource dataSource) {this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); }public int countOfActorsByFirstName(String firstName) {String sql = "select count(0) from T_ACTOR where first_name = :first_name";SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);return namedParameterJdbcTemplate.queryForInt(sql, namedParameters); }注意這里在'sql'中使用了命名參數作為變量,而這個名稱所對應的值被定義在傳入的'namedParameters' 中作為參數(也可以傳入到MapSqlParameterSource中作為參數)。
你也可以傳入許多命名參數以及他們所對應的值,以Map的方式,作為鍵值對傳入到NamedParameterJdbcTemplate中。 (其余的被NamedParameterJdbcOperations所暴露的接口以及NamedParameterJdbcTemplate實現類遵循了類似的方式,此處不包含相關內容)。
// some JDBC-backed DAO class... private NamedParameterJdbcTemplate namedParameterJdbcTemplate;public void setDataSource(DataSource dataSource) {this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); }public int countOfActorsByFirstName(String firstName) {String sql = "select count(0) from T_ACTOR where first_name = :first_name";Map namedParameters = Collections.singletonMap("first_name", firstName);return this.namedParameterJdbcTemplate.queryForInt(sql, namedParameters); }NamedParameterJdbcTemplate類所具備的另外一個比較好的特性就是可以接收SqlParameterSource作為傳入參數 (這個類位于相同的包定義中)。 你已經在先前的一個例子中看到了這個接口的一個具體實現類。( MapSqlParameterSource類)。而SqlParameterSource 這個接口對于NamedParameterJdbcTemplate類的操作而言是一個傳入的參數。MapSqlParameterSource只是一個非常簡單的實現,使用了java.util.Map作為轉接器, 其中,Map中的Key表示參數名稱,而Map中的Value表示參數值。
另外一個SqlParameterSource 的實現類是BeanPropertySqlParameterSource。這個類對傳統的Java進行了封裝(也就是那些符合JavaBean標準的類), 并且使用了JavaBean的屬性作為參數的名稱和值。
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... private NamedParameterJdbcTemplate namedParameterJdbcTemplate;public void setDataSource(DataSource dataSource) {this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); }public int countOfActors(Actor exampleActor) {// notice how the named parameters match the properties of the above 'Actor' classString sql = "select count(0) from T_ACTOR where first_name = :firstName and last_name = :lastName";SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);return this.namedParameterJdbcTemplate.queryForInt(sql, namedParameters); }注意,NamedParameterJdbcTemplate類只是封裝了JdbcTemplate模板; 因而如果你需要訪問相應被封裝的JdbcTemplate類,并訪問一些只有在JdbcTemplate中擁有的功能,你需要使用getJdbcOperations()方法來進行訪問。
請參照Section?11.2.1.2, “JdbcTemplate 的最佳實踐”來獲取一些使用NamedParameterJdbcTemplate的最佳實踐。
11.2.3.?SimpleJdbcTemplate類
| Note | |
| SimpleJdbcTemplate所提供的一些特性必須工作在Java 5及以上版本。 | |
SimpleJdbcTemplate類是對JdbcTemplate類進行的封裝,從而可以充分利用Java 5所帶來的varargs和autoboxing等特性。 SimpleJdbcTemplate類完全利用了Java 5語法所帶來的蜜糖效應。凡是使用過Java 5的程序員們如果要從Java 5遷移回之前的JDK版本,無疑會發現這些特性所帶來的蜜糖效應。
“before and after”示例可以成為SimpleJdbcTemplate類所帶來的蜜糖效應的最佳詮釋。 下面的代碼示例首先展示了使用傳統的JdbcTemplate進行JDBC訪問的代碼,接著是使用SimpleJdbcTemplate類做同樣的事情。
// classic JdbcTemplate-style... private JdbcTemplate jdbcTemplate;public void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource); }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("id"));actor.setFirstName(rs.getString("first_name"));actor.setLastName(rs.getString("last_name"));return actor;}};// notice the cast, the wrapping up of the 'id' argument// in an array, and the boxing of the 'id' argument as a reference typereturn (Actor) jdbcTemplate.queryForObject(sql, mapper, new Object[] {Long.valueOf(id)}); }下面是同樣的邏輯,使用了SimpleJdbcTemplate;可以看到代碼“干凈”多了:
// SimpleJdbcTemplate-style... private SimpleJdbcTemplate simpleJdbcTemplate;public void setDataSource(DataSource dataSource) {this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); }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 typespublic 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;}};return this.simpleJdbcTemplate.queryForObject(sql, mapper, id); }請同樣參照Section?11.2.1.2, “JdbcTemplate 的最佳實踐”來獲取一些SimpleJdbcTemplate的最佳實踐
| Note | |
| SimpleJdbcTemplate只是提供了JdbcTemplate所提供的功能的子類。 如果你需要使用JdbcTemplate的方法,而這些方法又沒有在SimpleJdbcTemplate中定義,你需要調用getJdbcOperations()方法 獲取相應的方法調用。JdbcOperations接口中定義的方法需要在這邊做強制轉化才能使用。 | |
11.2.4.?DataSource接口
為了從數據庫中取得數據,我們首先需要獲取一個數據庫連接。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進行映射。
-
使用fallback翻譯器。SQLStateSQLExceptionTranslator類是缺省的fallback翻譯器。
?
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中獲取錯誤代碼集。
11.2.6.?執行SQL語句
我們僅需要非常少的代碼就可以達到執行SQL語句的目的,一旦獲得一個 DataSource和一個JdbcTemplate, 我們就可以使用JdbcTemplate提供的豐富功能實現我們的操作。下面的例子使用了極少的代碼完成創建一張表的工作。
import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate;public class ExecuteAStatement {private JdbcTemplate jdbcTemplate;public void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);}public void doExecute() {this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");} }11.2.7.?執行查詢
除了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 jdbcTemplate;public void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);}public int getCount() {return this.jdbcTemplate.queryForInt("select count(*) from mytable");}public String getName() {return (String) this.jdbcTemplate.queryForObject("select name from mytable", String.class);}public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;} }除了返回單值的查詢方法,JdbcTemplate還提供了一組返回List結果 的方法。List中的每一項對應查詢返回結果中的一行。其中最簡單的是queryForList方法, 該方法將返回一個List,該List中的每一條 記錄是一個Map對象,對應應數據庫中某一行;而該Map 中的每一項對應該數據庫行中的某一列值。下面的代碼片斷接著上面的例子演示了如何用該方法返回表中所有記錄:
private JdbcTemplate jdbcTemplate;public void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource); }public List getList() {return this.jdbcTemplate.queryForList("select * from mytable"); }返回的結果集類似下面這種形式:
[{name=Bob, id=1}, {name=Mary, id=2}]11.2.8.?更新數據庫
JdbcTemplate還提供了一些更新數據庫的方法。 在下面的例子中,我們根據給定的主鍵值對指定的列進行更新。 例子中的SQL語句中使用了“?”占位符來接受參數(這種做法在更新和查詢SQL語句中很常見)。 傳遞的參數值位于一個對象數組中(基本類型需要被包裝成其對應的對象類型)。
import javax.sql.DataSource;import org.springframework.jdbc.core.JdbcTemplate;public class ExecuteAnUpdate {private JdbcTemplate jdbcTemplate;public void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);}public void setName(int id, String name) {this.jdbcTemplate.update("update mytable set name = ? where id = ?", new Object[] {name, new Integer(id)});} }11.2.9.?獲取自動生成的主鍵
JdbcTemplate中有一個update方法,可以方便地從數據庫中獲取數據庫自動創建的主鍵。(這是JDBC 3.0的標準 - 可以參見13.6節獲取詳細信息)。 這個方法使用了PreparedStatementCreator接口作為第一個參數, 可以通過這個接口的實現類來定義相應的Insert語句。另外一個參數是KeyHolder, 一旦update方法成功,這個參數將包含生成的主鍵。這里對于創建合適的PreparedStatement并沒有一個統一的標準。(這也解釋了函數簽名如此定義的原因)。下面是一個在Oracle上運行良好的示例,它可能在其他平臺上無法工作:
final String INSERT_SQL = "insert into my_test (name) values(?)"; final String name = "Rob";KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(new PreparedStatementCreator() {public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {PreparedStatement ps =connection.prepareStatement(INSERT_SQL, new String[] {"id"});ps.setString(1, name);return ps;}},keyHolder);// keyHolder.getKey() now contains the generated key11.3.?控制數據庫連接
11.3.1.?DataSourceUtils類
DataSourceUtils作為一個幫助類提供易用且強大的數據庫訪問能力, 我們可以使用該類提供的靜態方法從JNDI獲取數據庫連接以及在必要的時候關閉之。 它提供支持線程綁定的數據庫連接(比如使用DataSourceTransactionManager的時候,將把數據庫連接綁定到當前的線程上)。
11.3.2.?SmartDataSource接口
SmartDataSource是DataSource 接口的一個擴展,用來提供數據庫連接。使用該接口的類在指定的操作之后可以檢查是否需要關閉連接。該接口在某些情況下非常有用,比如有些情況需要重用數據庫連接。
11.3.3.?AbstractDataSource類
AbstractDataSource是一個實現了DataSource 接口的abstract基類。它實現了DataSource接口的 一些無關痛癢的方法,如果你需要實現自己的DataSource,那么可以繼承該類。
11.3.4.?SingleConnectionDataSource類
SingleConnectionDataSource是SmartDataSource接口 的一個實現,其內部包裝了一個單連接。該連接在使用之后將不會關閉,很顯然它不能在多線程的環境下使用。
當客戶端代碼調用close方法的時候,如果它總是假設數據庫連接來自連接池(就像使用持久化工具時一樣), 你應該將suppressClose設置為true。這樣,通過該類獲取的將是代理連接(禁止關閉)而不是原有的物理連接。 需要注意的是,我們不能把使用該類獲取的數據庫連接造型(cast)為Oracle Connection之類的本地數據庫連接。
SingleConnectionDataSource主要在測試的時候使用。它使得測試代碼很容易脫離應用服務器而在一個簡單的JNDI環境下運行。 與DriverManagerDataSource不同的是,它始終只會使用同一個數據庫連接,從而避免每次建立物理連接的開銷。
11.3.5.?DriverManagerDataSource類
DriverManagerDataSource類實現了 SmartDataSource接口。可以使用bean properties來設置JDBC Driver屬性,該類每次返回的都是一個新的連接。
該類主要在測試以及脫離J2EE容器的獨立環境中使用。它既可以用來在application context中作為一個DataSource bean,也可以在簡單的JNDI環境下使用。 由于Connection.close()僅僅只是簡單的關閉數據庫連接,因此任何能夠獲取DataSource的持久化代碼都能很好的工作。不過使用JavaBean風格的連接池 (比如commons-dbcp)也并非難事。即使是在測試環境下,使用連接池也是一種比使用DriverManagerDataSource更好的做法。
11.3.6.?TransactionAwareDataSourceProxy類
TransactionAwareDataSourceProxy作為目標DataSource的一個代理, 在對目標DataSource包裝的同時,還增加了Spring的事務管理能力, 在這一點上,這個類的功能非常像J2EE服務器所提供的事務化的JNDI DataSource。
| Note | |
| 該類幾乎很少被用到,除非現有代碼在被調用的時候需要一個標準的 JDBC DataSource接口實現作為參數。 這種情況下,這個類可以使現有代碼參與Spring的事務管理。通常最好的做法是使用更高層的抽象 來對數據源進行管理,比如JdbcTemplate和DataSourceUtils等等。 | |
如果需要更詳細的資料,請參考 TransactionAwareDataSourceProxy JavaDocs。
11.3.7.?DataSourceTransactionManager類
DataSourceTransactionManager類是 PlatformTransactionManager接口的一個實現,用于處理單JDBC數據源。 它將從指定DataSource取得的JDBC連接綁定到當前線程,因此它也支持了每個數據源對應到一個線程。
我們推薦在應用代碼中使用DataSourceUtils.getConnection(DataSource)來獲取 JDBC連接,而不是使用J2EE標準的DataSource.getConnection。因為前者將拋出 unchecked的org.springframework.dao異常,而不是checked的 SQLException異常。Spring Framework中所有的類(比如 JdbcTemplate)都采用這種做法。如果不需要和這個 DataSourceTransactionManager類一起使用,DataSourceUtils 提供的功能跟一般的數據庫連接策略沒有什么兩樣,因此它可以在任何場景下使用。
DataSourceTransactionManager類支持定制隔離級別,以及對SQL語句查詢超時的設定。 為了支持后者,應用代碼必須使用JdbcTemplate或者在每次創建SQL語句時調用DataSourceUtils.applyTransactionTimeout(..)方法。
在使用單個數據源的情形下,你可以用DataSourceTransactionManager來替代JtaTransactionManager, 因為DataSourceTransactionManager不需要容器支持JTA。如果你使用DataSourceUtils.getConnection(DataSource)來獲取 JDBC連接,二者之間的切換只需要更改一些配置。最后需要注意的一點就是JtaTransactionManager不支持隔離級別的定制!
11.3.8.?NativeJdbcExtractor
有時我們需要執行特殊的,由特定廠商提供的與標準JDBC的API不同的JDBC方法。此時,當我們在某個應用服務器上運行包裝了這些廠商各自實現的Connection, Statement和ResultSet對象的DataSource 時,可能會遇到一些問題。如果你要訪問這些對象,你可以配置一個包含NativeJdbcExtractor的JdbcTemplate或者OracleLobHandler。
NativeJdbcExtractor根據執行環境的不同,會有不同的風格的實現:
-
SimpleNativeJdbcExtractor
-
C3P0NativeJdbcExtractor
-
CommonsDbcpNativeJdbcExtractor
-
JBossNativeJdbcExtractor
-
WebLogicNativeJdbcExtractor
-
WebSphereNativeJdbcExtractor
-
XAPoolNativeJdbcExtractor
通常來說SimpleNativeJdbcExtractor類對于絕大多數環境,已經足以屏蔽 Connection 對象。可以參見Java Docs獲取詳細信息。
11.4.?JDBC批量操作
絕大多數JDBC驅動針對批量調用相同的prepared statement對象提供了性能提升。通過將這些更新操作封裝到一個批量操作中,可以大量減少與數據庫的操作頻繁度。 本章節將詳細描述使用JdbcTemplate或者SimpleJdbcTemplate進行批量操作的流程。
11.4.1.?使用JdbcTemplate進行批量操作
JdbcTemplate的批量操作特性需要實現特定的接口BatchPreparedStatementSetter來進行的, 通過實現這個接口,并將其傳入batchUpdate方法進行調用。 這個接口有兩個方法需要實現。一個叫做getBatchSize來提供當前需要批量操作的數量。另外一個方法是setValues 允許你為prepared statement設置參數。這個方法將在整個過程中被調用的次數,則取決于你在getBatchSize中所指定的大小。 下面的示例展示了根據傳入的list參數更新actor表,而傳入的list同時作為批量操作的參數。
?
public class JdbcActorDao implements ActorDao {private JdbcTemplate jdbcTemplate;public void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);}public int[] batchUpdate(final List actors) {int[] updateCounts = jdbcTemplate.batchUpdate("update t_actor set first_name = ?, last_name = ? where id = ?",new BatchPreparedStatementSetter() {public void setValues(PreparedStatement ps, int i) throws SQLException {ps.setString(1, ((Actor)actors.get(i)).getFirstName());ps.setString(2, ((Actor)actors.get(i)).getLastName());ps.setLong(3, ((Actor)actors.get(i)).getId().longValue());}public int getBatchSize() {return actors.size();}} );return updateCounts;}// ... additional methods }如果你是通過讀取文件進行批量操作,那么你可能需要一個特定的批量操作的數量,不過最后一次的批量操作,你可能沒有那么多數量的記錄。 在這種情況下,你可以實現InterruptibleBatchPreparedStatementSetter接口,從而允許你在某些情況中斷批量操作,isBatchExhausted 方法允許你指定一個終止批量操作的信號量。
11.4.2.?使用SimpleJdbcTemplate進行批量操作
SimpleJdbcTemplate類提供了另外一種批量操作的方式。無需實現一個特定的接口,你只需要提供所有在調用過程中要用到的參數,框架會遍歷這些參數值,并使用內置的prepared statement類進行批量操作。API將根據你是否使用命名參數而有所不同。對于使用命名參數的情況,你需要提供一個SqlParameterSource的數組, 其中的每個元素將將作為批量操作的參數。 你可以使用SqlParameterSource.createBatch方法,通過傳入一個JavaBean的數組或者一個包含了參數鍵值對的Map數組來創建這個數組。
下面的示例展示了使用命名參數進行批量更新的方法:
?
public class JdbcActorDao implements ActorDao {private SimpleJdbcTemplate simpleJdbcTemplate;public void setDataSource(DataSource dataSource) {this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);}public int[] batchUpdate(final List<Actor> actors) {SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(actors.toArray());int[] updateCounts = simpleJdbcTemplate.batchUpdate("update t_actor set first_name = :firstName, last_name = :lastName where id = :id",batch);return updateCounts;}// ... additional methods }對于使用傳統的“?”作為參數占位符的情況,你可以傳入一個List,包含了所有需要進行批量更新的對象。這樣的對象數組必須與每個SQL Statement的占位符以及他們在SQL Statement中出現的位置一一對應。
下面是同樣的例子,使用的傳統的“?”作為參數占位符:
?
public class JdbcActorDao implements ActorDao {private SimpleJdbcTemplate simpleJdbcTemplate;public void setDataSource(DataSource dataSource) {this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);}public int[] batchUpdate(final List<Actor> actors) {List<Object[]> batch = new ArrayList<Object[]>();for (Actor actor : actors) {Object[] values = new Object[] {actor.getFirstName(),actor.getLastName(),actor.getId()};batch.add(values);}int[] updateCounts = simpleJdbcTemplate.batchUpdate("update t_actor set first_name = ?, last_name = ? where id = ?",batch);return updateCounts;}// ... additional methods }所有的批量更新的方法都會返回一組int的數組,表示在整個操作過程中有多少記錄被批量更新。 這個數量是由JDBC驅動所返回的,有時這個返回并不可靠,尤其對于某些JDBC驅動只是簡單的返回-2作為返回值。
11.5.?通過使用SimpleJdbc類簡化JDBC操作
SimpleJdbcInsert類和SimpleJdbcCall類主要利用了JDBC驅動所提供的數據庫元數據的一些特性來簡化數據庫操作配置。 這意味著你可以盡可能的簡化你的數據庫操作配置。當然,你可以可以將元數據處理的特性關閉,從而在你的代碼中詳細指定這些特性。
11.5.1.?使用SimpleJdbcInsert插入數據
讓我們從SimpleJdbcInsert類開始。我們將盡可能使用最少量的配置。SimpleJdbcInsert類必須在數據訪問層的初始化方法中被初始化。 在這個例子中,初始化方法為setDataSource方法。你無需繼承自SimpleJdbcInsert 類,只需要創建一個新的實例,并通過withTableName方法設置table名稱。 這個類使用了“fluid”模式返回SimpleJdbcInsert,從而你可以訪問到所有的可以進行配置的方法。在這個例子中,我們只使用了一個方法,稍后我們會看到更多的配置方法。
public class JdbcActorDao implements ActorDao {private SimpleJdbcTemplate simpleJdbcTemplate;private SimpleJdbcInsert insertActor;public void setDataSource(DataSource dataSource) {this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");}public void add(Actor actor) {Map<String, Object> parameters = new HashMap<String, Object>(3);parameters.put("id", actor.getId());parameters.put("first_name", actor.getFirstName());parameters.put("last_name", actor.getLastName());insertActor.execute(parameters);}// ... additional methods }這個方法通過接收 java.utils.Map 作為它唯一的參數。 在這里需要重點注意的是,在Map中的所有的key,必須和數據庫中定義的列名完全匹配。這是因為我們是通過讀取元數據來構造實際的Insert語句的。
11.5.2.?使用SimpleJdbcInsert來獲取自動生成的主鍵
接下來,我們對于同樣的插入語句,我們并不傳入id,而是通過數據庫自動獲取主鍵的方式來創建新的Actor對象并插入數據庫。 當我們創建SimpleJdbcInsert實例時, 我們不僅需要指定表名,同時我們通過usingGeneratedKeyColumns方法指定需要數據庫自動生成主鍵的列名。
?
public class JdbcActorDao implements ActorDao {private SimpleJdbcTemplate simpleJdbcTemplate;private SimpleJdbcInsert insertActor;public void setDataSource(DataSource dataSource) {this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);this.insertActor =new SimpleJdbcInsert(dataSource).withTableName("t_actor").usingGeneratedKeyColumns("id");}public void add(Actor actor) {Map<String, Object> parameters = new HashMap<String, Object>(2);parameters.put("first_name", actor.getFirstName());parameters.put("last_name", actor.getLastName());Number newId = insertActor.executeAndReturnKey(parameters);actor.setId(newId.longValue());}// ... additional methods }這樣我們可以看到與之前執行的insert操作的不同之處在于,我們無需添加id到參數的Map中去,只需要調用executeReturningKey方法。 這個方法將返回一個java.lang.Number對象,我們可以使用這個對象來創建一個字符類型的實例作為我們的域模型的屬性。 有一點很重要的地方需要注意,我們無法依賴所有的數據庫來返回我們指定的Java類型,因為我們只知道這些對象的基類是java.lang.Number。 如果你有聯合主鍵或者那些非數字類型的主鍵需要生成,那么你可以使用executeReturningKeyHolder方法返回的KeyHolder對象。
11.5.3.?指定SimpleJdbcInsert所使用的字段
通過指定所使用的字段名稱,可以使SimpleJdbcInsert僅使用這些字段作為insert語句所使用的字段。這可以通過usingColumns方法進行配置。
?
public class JdbcActorDao implements ActorDao {private SimpleJdbcTemplate simpleJdbcTemplate;private SimpleJdbcInsert insertActor;public void setDataSource(DataSource dataSource) {this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);this.insertActor =new SimpleJdbcInsert(dataSource).withTableName("t_actor").usingColumns("first_name", "last_name").usingGeneratedKeyColumns("id");}public void add(Actor actor) {Map<String, Object> parameters = new HashMap<String, Object>(2);parameters.put("first_name", actor.getFirstName());parameters.put("last_name", actor.getLastName());Number newId = insertActor.executeAndReturnKey(parameters);actor.setId(newId.longValue());}// ... additional methods }執行這樣的insert語句所使用的字段,與之前我們所依賴的數據庫元數據是一致的。
11.5.4.?使用SqlParameterSource提供參數值
使用Map來指定參數值有時候工作得非常好,但是這并不是最簡單的使用方式。Spring提供了一些其他的SqlParameterSource實現類來指定參數值。 我們首先可以看看BeanPropertySqlParameterSource類,這是一個非常簡便的指定參數的實現類,只要你有一個符合JavaBean規范的類就行了。它將使用其中的getter方法來獲取參數值。 下面是一個例子:
?
public class JdbcActorDao implements ActorDao {private SimpleJdbcTemplate simpleJdbcTemplate;private SimpleJdbcInsert insertActor;public void setDataSource(DataSource dataSource) {this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);this.insertActor =new SimpleJdbcInsert(dataSource).withTableName("t_actor").usingGeneratedKeyColumns("id");}public void add(Actor actor) {SqlParameterSource parameters = new BeanPropertySqlParameterSource(actor);Number newId = insertActor.executeAndReturnKey(parameters);actor.setId(newId.longValue());}// ... additional methods }另外一個實現類:MapSqlParameterSource也使用Map來指定參數,但是他另外提供了一個非常簡便的addValue方法,可以被連續調用,來增加參數。
?
public class JdbcActorDao implements ActorDao {private SimpleJdbcTemplate simpleJdbcTemplate;private SimpleJdbcInsert insertActor;public void setDataSource(DataSource dataSource) {this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);this.insertActor =new SimpleJdbcInsert(dataSource).withTableName("t_actor").usingGeneratedKeyColumns("id");}public void add(Actor actor) {SqlParameterSource parameters = new MapSqlParameterSource().addValue("first_name", actor.getFirstName()).addValue("last_name", actor.getLastName());Number newId = insertActor.executeAndReturnKey(parameters);actor.setId(newId.longValue());}// ... additional methods }正如你看到的,配置是一樣的,區別只是切換了不同的提供參數的實現方式來執行調用。
11.5.5.?使用SimpleJdbcCall調用存儲過程
接下來我們把我們的關注點放在使用 SimpleJdbcCall 來進行存儲過程的調用上。 設計這個類的目的在于使得調用存儲過程盡可能簡單。它同樣利用的數據庫元數據的特性來查找傳入的參數和返回值。 這意味著你無需明確聲明那些參數。當然,如果你喜歡,可以依然聲明這些參數,尤其對于某些參數,你無法直接將他們映射到Java類上,例如ARRAY類型和STRUCT類型的參數。 在我們的第一個示例中,我們可以看到一個簡單的存儲過程調用,它僅僅返回VARCHAR和DATE類型。 這里,我特地為Actor類增加了一個birthDate的屬性,從而可以使得返回值擁有不同的數據類型。 這個存儲過程讀取actor的主鍵,并返回first_name,last_name,和birth_date字段作為返回值。 下面是這個存儲過程的源碼,它可以工作在MySQL數據庫上:
?
CREATE PROCEDURE read_actor ( IN in_id INTEGER, OUT out_first_name VARCHAR(100), OUT out_last_name VARCHAR(100), OUT out_birth_date DATE) BEGIN SELECT first_name, last_name, birth_date INTO out_first_name, out_last_name, out_birth_date FROM t_actor where id = in_id; END;正如你看到的,這里有四個參數,其中一個是傳入的參數“in_id”,表示了Actor的主鍵,剩下的參數是作為讀取數據庫表中的數據所返回的返回值。
SimpleJdbcCall的聲明與SimpleJdbcInsert類似,你無需繼承這個類,而只需要在初始化方法中進行初始化。 在這里例子中,我們只需要指定存儲過程的名稱。
?
public class JdbcActorDao implements ActorDao {private SimpleJdbcTemplate simpleJdbcTemplate;private SimpleJdbcCall procReadActor;public void setDataSource(DataSource dataSource) {this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);this.procReadActor =new SimpleJdbcCall(dataSource).withProcedureName("read_actor");}public Actor readActor(Long id) {SqlParameterSource in = new MapSqlParameterSource().addValue("in_id", id); Map out = procReadActor.execute(in);Actor actor = new Actor();actor.setId(id);actor.setFirstName((String) out.get("out_first_name"));actor.setLastName((String) out.get("out_last_name"));actor.setBirthDate((Date) out.get("out_birth_date"));return actor;}// ... additional methods }通過SimpleJdbcCall執行存儲過程需要創建一個SqlParameterSource的實現類來指定傳入的參數。 需要注意的是,傳入參數的名稱與存儲過程中定義的名稱必須保持一致。這里,我們無需保持一致,因為我們使用數據庫的元數據信息來決定我們需要什么樣的數據庫對象。 當然,你在源代碼中所指定的名稱可能和數據庫中完全不同,有的數據庫會把這些名稱全部轉化成大寫,而有些數據庫會把這些名稱轉化為小寫。
execute方法接收傳入的參數,并返回一個Map作為返回值,這個Map包含所有在存儲過程中指定的參數名稱作為key。 在這個例子中,他們分別是out_first_name,out_last_name和 out_birth_date。
execute方法的最后部分是使用存儲過程所返回的值創建一個新的Actor實例。 同樣的,這里我們將名稱與存儲過程中定義的名稱保持一致非常重要。在這個例子中,在返回的Map中所定義的key值和數據庫的存儲過程中定義的值一致。 你可能需要在這里指定Spring使用Jakarta Commons提供的CaseInsensitiveMap。這樣做,你需要在創建你自己的JdbcTemplate類時,設置setResultsMapCaseInsensitive屬性為True。 然后,你將這個自定義的JdbcTemplate傳入SimpleJdbcCall的構造函數。當然,你需要把commons-collections.jar加入到classpath中去。 下面是配置示例:
?
public class JdbcActorDao implements ActorDao {private SimpleJdbcCall procReadActor;public void setDataSource(DataSource dataSource) {JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);jdbcTemplate.setResultsMapCaseInsensitive(true);this.procReadActor =new SimpleJdbcCall(jdbcTemplate).withProcedureName("read_actor");}// ... additional methods }通過這樣的配置,你就可以無需擔心返回參數值的大小寫問題。
11.5.6.?聲明SimpleJdbcCall使用的參數
你已經看到如何通過元數據來簡化參數配置,但是你也可以明確地指定這些參數。可以在創建SimpleJdbcCall時,通過使用declareParameters方法來聲明參數。 這個方法接收一組SqlParameter對象作為參數。我們可以參照下一個章節,如何創建SqlParameter。
我們可以有選擇性的顯示聲明一個、多個、甚至所有的參數。參數元數據在這里會被同時使用。 通過調用withoutProcedureColumnMetaDataAccess方法,我們可以指定數據庫忽略所有的元數據處理并使用顯示聲明的參數。 另外一種情況是,其中的某些參數值具有默認的返回值,我們需要在返回值中指定這些返回值。為了實現這個特性,我們可以使用useInParameterNames來指定一組需要被包含的參數名稱。
這是一個完整的聲明存儲過程調用的例子:
?
public class JdbcActorDao implements ActorDao {private SimpleJdbcCall procReadActor;public void setDataSource(DataSource dataSource) {JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);jdbcTemplate.setResultsMapCaseInsensitive(true);this.procReadActor =new SimpleJdbcCall(jdbcTemplate).withProcedureName("read_actor").withoutProcedureColumnMetaDataAccess().useInParameterNames("in_id").declareParameters(new SqlParameter("in_id", Types.NUMERIC),new SqlOutParameter("out_first_name", Types.VARCHAR),new SqlOutParameter("out_last_name", Types.VARCHAR),new SqlOutParameter("out_birth_date", Types.DATE));}// ... additional methods }執行和最終的返回處理是相同的,我們在這里只是明確聲明了參數類型,而不是依賴數據庫元數據特性。 這一點很重要,尤其對于那些數據庫并不支持元數據的情況。當前,我們支持元數據的特性的數據包含:Apache Derby、DB2、MySQL、 Microsoft SQL Server、Oracle和Sybase。我們同時對某些數據庫內置函數支持元數據特性:MySQL、Microsoft SQL Server和Oracle。
11.5.7.?如何定義SqlParameters
為SimpleJdbc類或者后續章節提到的RDBMS操作指定參數,你需要使用SqlParameter或者他的子類。 你可以通過指定參數名稱以及對應的SQL類型并傳入構造函數作為參數來指定SqlParameter,其中,SQL類型是java.sql.Types中所定義的常量。 我們已經看到過類似的聲明:
?
new SqlParameter("in_id", Types.NUMERIC),new SqlOutParameter("out_first_name", Types.VARCHAR),?
第一行的SqlParameter定義了一個傳入參數。傳入參數可以在所有的存儲過程中使用,也可以在稍后章節中提到的SqlQuery類及其子類中使用。
第二行SqlOutParameter定義了一個返回值。它可以被存儲過程調用所使用。當然,還有一個SqlInOutParameter類,可以用于輸入輸出參數。 也就是說,它既是一個傳入參數,也是一個返回值。
除了參數名稱和SQL類型,你還可以聲明一些其他額外的選項。對于傳入參數,你可以為numeric數據類型指定精度,或者對于特定的數據庫指定特殊類型。 對于返回值,你可以提供一個RowMapper接口來處理所有從REF cursor返回的列。另外一個選項是指定一個SqlReturnType類,從而可以定制返回值的處理方式。
11.5.8.?使用SimpleJdbcCall調用內置函數
內置函數的調用幾乎和存儲過程的調用是一樣的。唯一的不同在于,你需要聲明的是一個函數的名稱而不是存儲過程的名稱。 這可以通過withFunctionName方法來完成。使用這個方法,表明你的調用是一個函數。你所指定的這個函數名稱將被作為調用對象。 同時有一個叫做executeFunction的方法,將獲得特定的Java類型的函數調用的返回值。 此時,你無需通過返回的Map來獲取返回值。另外有一個類似的便捷方法executeObject用于存儲過程,但是他只能處理單個返回值的情況。 下面的示例展示了一個叫做get_actor_name 的函數調用,返回actor的完整的名稱。 這個函數將工作在MySQL數據庫上。
?
CREATE FUNCTION get_actor_name (in_id INTEGER) RETURNS VARCHAR(200) READS SQL DATA BEGINDECLARE out_name VARCHAR(200);SELECT concat(first_name, ' ', last_name)INTO out_nameFROM t_actor where id = in_id;RETURN out_name; END;?
調用這個函數,我們依然在初始化方法中創建SimpleJdbcCall
?
public class JdbcActorDao implements ActorDao {private SimpleJdbcTemplate simpleJdbcTemplate;private SimpleJdbcCall funcGetActorName;public void setDataSource(DataSource dataSource) {this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);jdbcTemplate.setResultsMapCaseInsensitive(true);this.funcGetActorName =new SimpleJdbcCall(jdbcTemplate).withFunctionName("get_actor_name");}public String getActorName(Long id) {SqlParameterSource in = new MapSqlParameterSource().addValue("in_id", id); String name = funcGetActorName.executeFunction(String.class, in);return name;}// ... additional methods }被調用的函數返回一個String類型。
11.5.9.?使用SimpleJdbcCall返回的ResultSet/REF Cursor
期望通過調用存儲過程或者函數來返回ResultSet一直是一個問題。一些數據庫在JDBC結果處理中返回結果集,而另外一些數據庫則需要明確指定返回值的類型。 無論哪種方法,都需要在循環遍歷結果集時,做出一些額外的工作,從而處理每一條記錄。 通過SimpleJdbcCall,你可以使用returningResultSet方法,并定義一個RowMapper的實現類來處理特定的返回值。 當結果集在返回結果處理過程中沒有被定義名稱時,返回的結果集必須與定義的RowMapper的實現類指定的順序保持一致。 而指定的名字也會被用作返回結果集中的名稱。
在這個例子中,我們將使用一個存儲過程,它并不接收任何參數,返回t_actor表中的所有的行,下面是MySQL數據庫中的存儲過程源碼:
CREATE PROCEDURE read_all_actors() BEGINSELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a; END;要調用這個存儲過程,我們需要定義一個RowMapper的實現類。我們所使用的類遵循JavaBean的規范,所以我們可以使用ParameterizedBeanPropertyRowMapper作為實現類。 通過將相應的class類作為參數傳入到newInstance方法中,我們可以創建這個實現類。
?
public class JdbcActorDao implements ActorDao {private SimpleJdbcTemplate simpleJdbcTemplate;private SimpleJdbcCall procReadAllActors;public void setDataSource(DataSource dataSource) {this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);jdbcTemplate.setResultsMapCaseInsensitive(true);this.procReadAllActors =new SimpleJdbcCall(jdbcTemplate).withProcedureName("read_all_actors").returningResultSet("actors",ParameterizedBeanPropertyRowMapper.newInstance(Actor.class));}public List getActorsList() {Map m = procReadAllActors.execute(new HashMap<String, Object>(0));return (List) m.get("actors");}// ... additional methods }這個函數調用傳入一個空的Map進入,因為這里不需要任何的參數傳入。而函數調用所返回的結果集將返回的是Actors列表。
11.6.?用Java對象來表達JDBC操作
org.springframework.jdbc.object包下的類允許用戶以更加 面向對象的方式去訪問數據庫。比如說,用戶可以執行查詢并返回一個list, 該list作為一個結果集將把從數據庫中取出的列數據映射到業務對象的屬性上。 用戶也可以執行存儲過程,以及運行更新、刪除以及插入SQL語句。
| Note | |
| 在許多Spring開發人員中間存在有一種觀點,那就是下面將要提到的各種RDBMS操作類 (StoredProcedure類除外) 通常也可以直接使用JdbcTemplate相關的方法來替換。 相對于把一個查詢操作封裝成一個類而言,直接調用JdbcTemplate方法將更簡單而且更容易理解。 必須強調的一點是,這僅僅只是一種觀點而已, 如果你認為你可以從直接使用RDBMS操作類中獲取一些額外的好處,你不妨根據自己的需要和喜好進行不同的選擇。 | |
11.6.1.?SqlQuery類
SqlQuery是一個可重用、線程安全的類,它封裝了一個SQL查詢。 其子類必須實現newResultReader()方法,該方法用來在遍歷 ResultSet的時候能使用一個類來保存結果。 我們很少需要直接使用SqlQuery,因為其子類 MappingSqlQuery作為一個更加易用的實現能夠將結果集中的行映射為Java對象。 SqlQuery還有另外兩個擴展分別是 MappingSqlQueryWithParameters和UpdatableSqlQuery。
11.6.2.?MappingSqlQuery類
MappingSqlQuery是一個可重用的查詢抽象類,其具體類必須實現 mapRow(ResultSet, int)抽象方法來將結果集中的每一行轉換成Java對象。 下面這個例子演示了一個定制查詢,它將從客戶表中取得的數據映射到一個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;} }在上面的例子中,我們為用戶查詢提供了一個構造函數并為構造函數傳遞了一個 DataSource參數。在構造函數里面我們把 DataSource和一個用來返回查詢結果的SQL語句作為參數 調用父類的構造函數。SQL語句將被用于生成一個PreparedStatement對象, 因此它可以包含占位符來傳遞參數。而每一個SQL語句的參數必須通過調用 declareParameter方法來進行聲明,該方法需要一個 SqlParameter(封裝了一個字段名字和一個 java.sql.Types中定義的JDBC類型)對象作為參數。 所有參數定義完之后,我們調用compile()方法來對SQL語句進行預編譯。
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方法通過傳遞惟一參數id來返回一個客戶對象。 該方法內部在創建CustomerMappingQuery實例之后, 我們創建了一個對象數組用來包含要傳遞的查詢參數。這里我們只有唯一的一個 Integer參數。執行CustomerMappingQuery的 execute方法之后,我們得到了一個List,該List中包含一個 Customer對象,如果有對象滿足查詢條件的話。
11.6.3.?SqlUpdate類
SqlUpdate類封裝了一個可重復使用的SQL更新操作。 跟所有RdbmsOperation類一樣,SqlUpdate可以在SQL中定義參數。 該類提供了一系列update()方法,就像SqlQuery提供的一系列execute()方法一樣。 SqlUpdate是一個具體的類。通過在SQL語句中定義參數,這個類可以支持不同的更新方法,我們一般不需要通過繼承來實現定制。
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);} }11.6.4.?StoredProcedure類
StoredProcedure類是一個抽象基類,它是對RDBMS存儲過程的一種抽象。 該類提供了多種execute(..)方法,不過這些方法的訪問類型都是protected的。
從父類繼承的sql屬性用來指定RDBMS存儲過程的名字。 盡管該類提供了許多必須在JDBC3.0下使用的功能,但是我們更關注的是JDBC 3.0中引入的命名參數特性。
下面的程序演示了如何調用Oracle中的sysdate()函數。 這里我們創建了一個繼承StoredProcedure的子類,雖然它沒有輸入參數, 但是我必須通過使用SqlOutParameter來聲明一個日期類型的輸出參數。 execute()方法將返回一個map,map中的每個entry是一個用參數名作key,以輸出參數為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游標類型的輸出參數。
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構造函數中 declareParameter(..)的SqlOutParameter參數, 該參數使用了RowMapper接口的實現。這是一種非常方便而強大的重用方式。 下面我們來看一下RowMapper的兩個具體實現。
首先是TitleMapper類,它簡單的把ResultSet中的每一行映射為一個TitleDomain 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中的每一行映射為一個GenreDomain 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"));} }如果你需要給存儲過程傳輸入參數(這些輸入參數是在RDBMS存儲過程中定義好了的), 則需要提供一個指定類型的execute(..)方法, 該方法將調用基類的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);declareParameter(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);} }11.6.5.?SqlFunction類
SqlFunction RDBMS操作類封裝了一個SQL“函數”包裝器(wrapper), 該包裝器適用于查詢并返回一個單行結果集。默認返回的是一個int值, 不過我們可以采用類似JdbcTemplate中的queryForXxx 做法自己實現來返回其它類型。SqlFunction優勢在于我們不必創建 JdbcTemplate,這些它都在內部替我們做了。
該類的主要用途是調用SQL函數來返回一個單值的結果集,比如類似“select user()”、 “select sysdate from dual”的查詢。如果需要調用更復雜的存儲函數, (可以為這種類型的處理使用StoredProcedure或SqlCall)。
SqlFunction是一個具體類,通常我們不需要它的子類。 其用法是創建該類的實例,然后聲明SQL語句以及參數就可以調用相關的run方法去多次執行函數。 下面的例子用來返回指定表的記錄行數:
public int countRows() {SqlFunction sf = new SqlFunction(dataSource, "select count(*) from mytable");sf.compile();return sf.run(); }11.7.?參數和數據處理的基本原則
在Spring的JDBC框架的所有工作模式中貫徹了一些與參數和數據處理相關的基本原則。
11.7.1.?為參數設置SQL類型信息
多數情況下,Spring會根據傳入的參數值來設定相應的SQL類型。有時,我們有必要明確指定傳入參數所代表的SQL類型,這一點對于正確設置NULL值的時候可能比較有用。
另外還有一些其他的不同方面的作用:
-
多數JdbcTemplate的update或者query方法會接收一個額外的int數組構成的參數。 這個數組需要提供的是使用java.sql.Types中所定義的SQL類型。而這個數組中定義的類型需要與每個傳入的參數所對應。
-
你可以使用SqlParameterValue對參數進行額外的封裝從而包裝更多的參數信息。通過傳入參數值和對應的SQL類型作為構造函數的參數,你可以創建這個類的一個實例。 你也可以為numeric的值提供一些額外的精度要求。
-
對于那些使用命名參數的情況,你可以使用SqlParameterSource、BeanPropertySqlParameterSource或者MapSqlParameterSource類。 他們都具備了為命名參數注冊SQL類型的功能。
11.7.2.?處理BLOB 和 CLOB對象
你可以在數據庫中存儲圖像、二進制對象或者大文本等對象。這些較大的二進制對象被稱之為BLOB類型,而對應的大文本對象被稱之為CLOB對象。 Spring允許你使用JdbcTemplate、更高層次封裝的RDBMS對象和SimpleJdbc類對這些大對象進行操作。 所有的這些操作方式都實現了LobHandler接口來處理LOB類型的數據。 LobHandler接口提供了訪問LobCreator的方法,通過調用getLobCreator,你可以創建一個新的LOB類型的數據。
LobCreator/LobHandler接口針對LOB類型的數據操作提供了下列支持:
?
-
BLOB
-
byte[] – getBlobAsBytes and setBlobAsBytes
byte[] – getBlobAsBytes 和 setBlobAsBytes
-
InputStream – getBlobAsBinaryStream and setBlobAsBinaryStream
InputStream – getBlobAsBinaryStream和setBlobAsBinaryStream
-
-
CLOB
-
String – getClobAsString and setClobAsString
String – getClobAsString和setClobAsString
-
InputStream – getClobAsAsciiStream and setClobAsAsciiStream
InputStream – getClobAsAsciiStream和setClobAsAsciiStream
-
Reader – getClobAsCharacterStream and setClobAsCharacterStream
Reader – getClobAsCharacterStream和setClobAsCharacterStream
-
?
現在我們通過一個示例來展示如何創建一個BLOB數據并插入數據庫。稍后的例子,我們將展示如何從數據庫中將BLOB數據讀取出來。
這個例子使用JdbcTemplate和一個AbstractLobCreatingPreparedStatementCallback的實現類。 這里唯一需要實現的方法就是"setValues"。在這個方法中,將提供一個LobCreator接口,被用作在你的插入語句中設置LOB字段的值。
我們假設有一個變量叫做“lobHandler”已經被設置到DefaultLobHandler的實例中。當然,這是由注入完成的。
final File blobIn = new File("spring2004.jpg"); final InputStream blobIs = new FileInputStream(blobIn); final File clobIn = new File("large.txt"); final InputStream clobIs = new FileInputStream(clobIn); final InputStreamReader clobReader = new InputStreamReader(clobIs); jdbcTemplate.execute("INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)",new AbstractLobCreatingPreparedStatementCallback(lobhandler) { protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException {ps.setLong(1, 1L);lobCreator.setClobAsCharacterStream(ps, 2, clobReader, (int)clobIn.length()); lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, (int)blobIn.length()); }} ); blobIs.close(); clobReader.close();| 我們在這里使用的lobHandler實現類是一個普通的DefaultLobHandler | |
| 使用setClobAsCharacterStream,我們傳入CLOB的內容 | |
| 使用setBlobAsBinartStream,我們傳入BLOB的內容 |
現在我們來示范從數據庫中讀取LOB數據。我們這里再次使用JdbcTempate并使用相同的DefaultLobHandler實例。
List l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table",new RowMapper() {public Object mapRow(ResultSet rs, int i) throws SQLException {Map results = new HashMap();String clobText = lobHandler.getClobAsString(rs, "a_clob"); results.put("CLOB", clobText);byte[] blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob"); results.put("BLOB", blobBytes);return results;}});| 使用getClobAsString 獲取CLOB內容 | |
| 使用getBlobAsBytes獲取BLOB內容 |
11.7.3.?在IN語句中傳入一組參數值
SQL標準允許基于一個帶參數列表的表達式進行查詢。一個典型的例子可能像這樣:"select * from T_ACTOR where id in (1, 2, 3)"。 不過這種參數列表的方式并不能直接被JDBC標準所支持 - 因為并不存在這種聲明一個列表參數作為占位符的方式。 你不得不為此寫多個占位符來表示多個參數,或者當你知道占位符的數量時,你可以動態構建SQL字符串。 NamedParameterJdbcTemplate和SimpleJdbcTemplate中所提供的命名參數的特性,采用的是后面一種做法。 當你傳入參數時,你需要傳入一個java.util.List類型,支持基本類型。而這個list將會在SQL執行時替換占位符并傳入參數。
| Note | |
| 在使用IN語句時,當你傳入大批量的值時要小心,JDBC標準并不確保超過100個元素在IN語句中。 有不少數據庫可以超出這個值的限制,但是不同的數據庫會有不同的數量限制,比如Oracle的限制數量是1000個。 | |
除了基本類型之外,你還可以創建一個java.util.List的對象數組,這可以讓你支持在IN表達式中編寫多重表達式,例如"select * from T_ACTOR where (id, last_name) in ((1, 'Johnson'), (2, 'Harrop'))". 當然,這樣做的前提是數據庫底層的語法支持。
11.7.4.?處理復雜類型的存儲過程調用
當調用存儲過程時,有時需要使用數據庫特定的復雜類型。為了適應這些類型,Spring提供了SqlReturnType類來處理存儲過程的返回值,而使用SqlTypeValue來處理傳入的參數。
下面這個例子,將Oracle的STRUCT對象作為返回值,這是一個由用戶自定義的“ITEM_TYPE”。 SqlReturnType接口有唯一的方法“getTypeValue”需要被實現。而這個接口的實現將被用作SqlOutParameter聲明的一部分。
?
declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE",new SqlReturnType() {public Object getTypeValue(CallableStatement cs, int colIndx, int sqlType, String typeName) throws SQLException {STRUCT struct = (STRUCT)cs.getObject(colIndx);Object[] attr = struct.getAttributes();TestItem item = new TestItem();item.setId(((Number) attr[0]).longValue());item.setDescription((String)attr[1]);item.setExpirationDate((java.util.Date)attr[2]);return item;}}));通過Java代碼調用存儲過程使用SqlTypeValue來傳入一個TestItem作為參數。 SqlTypeValue接口有一個方法"createTypeValue"需要被實現。 一個活動的數據庫連接也同時被傳入,它將被用作創建數據庫特定的對象,類似StructDescriptor和ArrayDescriptor
?
SqlTypeValue value = new AbstractSqlTypeValue() {protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {StructDescriptor itemDescriptor = new StructDescriptor(typeName, conn);Struct item = new STRUCT(itemDescriptor, conn,new Object[] {testItem.getId(),testItem.getDescription(),new java.sql.Date(testItem.getExpirationDate().getTime())});return item;} };這里的SqlTypeValue 現在可以被加入到作為參數的Map中去,從而可以執行相應的存儲過程。
?
總結
以上是生活随笔為你收集整理的使用Spring JDBC进行数据访问 (JdbcTemplate/NamedParameterJdbcTemplate/SimpleJdbcTemplate/SimpleJdbcCall/Stor)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: spring 同时配置hibernate
- 下一篇: decode and nvl and s