Spring JDBC抽象框架所帶來的價值將在以下幾個方面得以體現(xiàn):(注:使用了Spring JDBC抽象框架之后,應(yīng)用開發(fā)人員只需要完成斜體字部分的編碼工作。)
定義數(shù)據(jù)庫連接參數(shù)
打開數(shù)據(jù)庫連接
聲明SQL語句
預(yù)編譯并執(zhí)行SQL語句
遍歷查詢結(jié)果(如果需要的話)
處理每一次遍歷操作
處理拋出的任何異常
處理事務(wù)
關(guān)閉數(shù)據(jù)庫連接
Spring將替我們完成所有使用JDBC API進行開發(fā)的單調(diào)乏味的、底層細(xì)節(jié)處理工作。
使用Spring進行基本的JDBC訪問數(shù)據(jù)庫有多種選擇。Spring至少提供了三種不同的工作模式:JdbcTemplate, 一個在Spring2.5中新提供的SimpleJdbc類能夠更好的處理數(shù)據(jù)庫元數(shù)據(jù); 還有一種稱之為RDBMS Object的風(fēng)格的面向?qū)ο蠓庋b方式, 有點類似于JDO的查詢設(shè)計。 我們在這里簡要列舉你采取某一種工作方式的主要理由. 不過請注意, 即使你選擇了其中的一種工作模式, 你依然可以在你的代碼中混用其他任何一種模式以獲取其帶來的好處和優(yōu)勢。 所有的工作模式都必須要求JDBC 2.0以上的數(shù)據(jù)庫驅(qū)動的支持, 其中一些高級的功能可能需要JDBC 3.0以上的數(shù)據(jù)庫驅(qū)動支持。
JdbcTemplate - 這是經(jīng)典的也是最常用的Spring對于JDBC訪問的方案。這也是最低級別的封裝, 其他的工作模式事實上在底層使用了JdbcTemplate作為其底層的實現(xiàn)基礎(chǔ)。JdbcTemplate在JDK 1.4以上的環(huán)境上工作得很好。
NamedParameterJdbcTemplate - 對JdbcTemplate做了封裝,提供了更加便捷的基于命名參數(shù)的使用方式而不是傳統(tǒng)的JDBC所使用的“?”作為參數(shù)的占位符。這種方式在你需要為某個SQL指定許多個參數(shù)時,顯得更加直觀而易用。該特性必須工作在JDK 1.4以上。
SimpleJdbcTemplate - 這個類結(jié)合了JdbcTemplate和NamedParameterJdbcTemplate的最常用的功能,同時它也利用了一些Java 5的特性所帶來的優(yōu)勢,例如泛型、varargs和autoboxing等,從而提供了更加簡便的API訪問方式。需要工作在Java 5以上的環(huán)境中。
SimpleJdbcInsert 和 SimpleJdbcCall - 這兩個類可以充分利用數(shù)據(jù)庫元數(shù)據(jù)的特性來簡化配置。通過使用這兩個類進行編程,你可以僅僅提供數(shù)據(jù)庫表名或者存儲過程的名稱以及一個Map作為參數(shù)。其中Map的key需要與數(shù)據(jù)庫表中的字段保持一致。這兩個類通常和SimpleJdbcTemplate配合使用。這兩個類需要工作在JDK 5以上,同時數(shù)據(jù)庫需要提供足夠的元數(shù)據(jù)信息。
RDBMS 對象包括MappingSqlQuery, SqlUpdate and StoredProcedure - 這種方式允許你在初始化你的數(shù)據(jù)訪問層時創(chuàng)建可重用并且線程安全的對象。該對象在你定義了你的查詢語句,聲明查詢參數(shù)并編譯相應(yīng)的Query之后被模型化。一旦模型化完成,任何執(zhí)行函數(shù)就可以傳入不同的參數(shù)對之進行多次調(diào)用。這種方式需要工作在JDK 1.4以上。
Spring Framework的JDBC抽象框架由四個包構(gòu)成:core、 dataSource、object以及support。
org.springframework.jdbc.core包由JdbcTemplate類以及相關(guān)的回調(diào)接口(callback interface)和類組成。 org.springframework.jdbc.core.simple 子包則包含了 SimpleJdbcTemplate 類以及相關(guān)的SimpleJdbcInsert 類和SimpleJdbcCall 類。 org.springframework.jdbc.core.namedparam 子包則包含了NamedParameterJdbcTemplate 類以及其相關(guān)的支持類。
org.springframework.jdbc.datasource包提供了一些工具類來簡化對DataSource的訪問。同時提供了多種簡單的DataSource實現(xiàn)。這些實現(xiàn)可以脫離J2EE容器進行獨立測試和運行。 這些工具類提供了一些靜態(tài)方法,允許你通過JNDI來獲取數(shù)據(jù)庫連接和關(guān)閉連接。同時支持綁定到當(dāng)前線程的數(shù)據(jù)庫連接。例如使用DataSourceTransactionManager。
接著org.springframework.jdbc.object包包含了一些類,用于將RDBMS查詢、更新以及存儲過程表述為一些可重用的、線程安全的對象。這種方式通過JDO進行模型化,不過這些通過查詢返回的對象是與數(shù)據(jù)庫脫離的對象。 這種對于JDBC的高層次的封裝是基于org.springframework.jdbc.core包對JDBC的低層次封裝之上的。
最后,org.springframework.jdbc.support包定義了SQLException轉(zhuǎn)化類以及一些其他的工具類。
在JDBC調(diào)用過程中所拋出的異常都會被轉(zhuǎn)化為在org.springframework.dao包中定義的異常。也就是說,凡是使用Spring的JDBC封裝層的代碼無需實現(xiàn)任何JDBC或者RDBMS相關(guān)的異常處理。所有的這些被轉(zhuǎn)化的異常都是unchecked異常,因而也給了你一種額外的選擇,你可以抓住這些異常,從而轉(zhuǎn)化成其他類型的異常被允許調(diào)用者傳播。
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ù)集的每一行中提取值。
我們可以在DAO實現(xiàn)類中通過傳遞一個DataSource引用來完成JdbcTemplate的實例化,也可以在Spring的IoC容器中配置一個JdbcTemplate的bean并賦予DAO實現(xiàn)類作為一個實例。 需要注意的是DataSource在Spring的IoC容器中總是配制成一個bean,第一種情況下,DataSource bean將傳遞給service,第二種情況下DataSource bean傳遞給JdbcTemplate bean。
最后,JdbcTemplate中使用的所有SQL將會以“DEBUG”級別記入日志(一般情況下日志的category是JdbcTemplate相應(yīng)的全限定類名,不過如果需要對JdbcTemplate進行定制的話,可能是它的子類名)。
下面是一些使用JdbcTemplate類的示例。(這些示例并不是完整展示所有的JdbcTemplate所暴露出來的功能。請查看與之相關(guān)的Javadoc)。
一個簡單的例子用于展示如何獲取一個表中的所有行數(shù)。
int rowCount = this.jdbcTemplate.queryForInt("select count(0) from t_accrual");
一個簡單的例子展示如何進行參數(shù)綁定。
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);
查詢并將結(jié)果記錄為一個簡單的數(shù)據(jù)模型。
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; } });
查詢并組裝多個數(shù)據(jù)模型。
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個示例中的代碼出現(xiàn)在同一段程序中,我們有必要去掉這些重復(fù)的RowMapper匿名類代碼,將這些代碼抽取到一個單獨的類中(通常是一個靜態(tài)的內(nèi)部類)。 這樣,這個內(nèi)部類就可以在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; }}
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)});
execute(..)方法可以被用作執(zhí)行任何類型的SQL,甚至是DDL語句。 這個方法的實現(xiàn)需要傳入一個回調(diào)接口、需要綁定的參數(shù)數(shù)組等作為參數(shù)。
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
調(diào)用一個簡單的存儲過程(更多復(fù)雜的存儲過程支持請參見存儲過程支持)。
this.jdbcTemplate.update( "call SUPPORT.REFRESH_ACTORS_SUMMARY(?)", new Object[]{Long.valueOf(unionId)});
JdbcTemplate類的實例是線程安全的實例。這一點非常重要,正因為如此,你可以配置一個簡單的JdbcTemplate實例,并將這個“共享的”、“安全的”實例注入到不同的DAO類中去。 另外, JdbcTemplate 是有狀態(tài)的,因為他所維護的DataSource 實例是有狀態(tài)的,但是這種狀態(tài)是無法變化的。
使用JdbcTemplate的一個常見的最佳實踐(同時也是SimpleJdbcTemplate和NamedParameterJdbcTemplate 類的最佳實踐)就是在Spring配置文件中配置一個DataSource實例,然后將這個共享的DataSource實例助于到你的DAO中去。 而JdbcTemplate的實例將在DataSource的setter方法中被創(chuàng)建。這樣的話,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...}
相關(guān)的配置看上去就像這樣。
<?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類你會發(fā)現(xiàn),這里只是提供了一個簡便的方式而已。
無論你是否使用上述這種初始化方式,都無需在執(zhí)行某些SQL操作時多次創(chuàng)建一個JdbcTemplate實例。記住,一旦JdbcTemplate被創(chuàng)建,他是一個線程安全的對象。 一個你需要創(chuàng)建多次JdbcTemplate實例的理由可能在于,你的應(yīng)用需要訪問多個不同的數(shù)據(jù)庫,從而需要不同的DataSources來創(chuàng)建不同的JdbcTemplates實例。
NamedParameterJdbcTemplate類為JDBC操作增加了命名參數(shù)的特性支持,而不是傳統(tǒng)的使用('?')作為參數(shù)的占位符。NamedParameterJdbcTemplate類對JdbcTemplate類進行了封裝, 在底層,JdbcTemplate完成了多數(shù)的工作。這一個章節(jié)將主要描述NamedParameterJdbcTemplate類與JdbcTemplate類的一些區(qū)別,也就是使用命名參數(shù)進行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'中使用了命名參數(shù)作為變量,而這個名稱所對應(yīng)的值被定義在傳入的'namedParameters' 中作為參數(shù)(也可以傳入到MapSqlParameterSource中作為參數(shù))。
你也可以傳入許多命名參數(shù)以及他們所對應(yīng)的值,以Map的方式,作為鍵值對傳入到NamedParameterJdbcTemplate中。 (其余的被NamedParameterJdbcOperations所暴露的接口以及NamedParameterJdbcTemplate實現(xiàn)類遵循了類似的方式,此處不包含相關(guān)內(nèi)容)。
// 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作為傳入?yún)?shù) (這個類位于相同的包定義中)。 你已經(jīng)在先前的一個例子中看到了這個接口的一個具體實現(xiàn)類。( MapSqlParameterSource類)。而SqlParameterSource 這個接口對于NamedParameterJdbcTemplate類的操作而言是一個傳入的參數(shù)。MapSqlParameterSource只是一個非常簡單的實現(xiàn),使用了java.util.Map作為轉(zhuǎn)接器, 其中,Map中的Key表示參數(shù)名稱,而Map中的Value表示參數(shù)值。
另外一個SqlParameterSource 的實現(xiàn)類是BeanPropertySqlParameterSource。這個類對傳統(tǒng)的Java進行了封裝(也就是那些符合JavaBean標(biāo)準(zhǔn)的類), 并且使用了JavaBean的屬性作為參數(shù)的名稱和值。
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' class String 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模板; 因而如果你需要訪問相應(yīng)被封裝的JdbcTemplate類,并訪問一些只有在JdbcTemplate中擁有的功能,你需要使用getJdbcOperations()方法來進行訪問。
請參照Section 11.2.1.2, “JdbcTemplate 的最佳實踐”來獲取一些使用NamedParameterJdbcTemplate的最佳實踐。
![]() | Note |
---|---|
SimpleJdbcTemplate所提供的一些特性必須工作在Java 5及以上版本。 |
SimpleJdbcTemplate類是對JdbcTemplate類進行的封裝,從而可以充分利用Java 5所帶來的varargs和autoboxing等特性。 SimpleJdbcTemplate類完全利用了Java 5語法所帶來的蜜糖效應(yīng)。凡是使用過Java 5的程序員們?nèi)绻獜腏ava 5遷移回之前的JDK版本,無疑會發(fā)現(xiàn)這些特性所帶來的蜜糖效應(yīng)。
“before and after”示例可以成為SimpleJdbcTemplate類所帶來的蜜糖效應(yīng)的最佳詮釋。 下面的代碼示例首先展示了使用傳統(tǒng)的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 type return (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 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; } }; return this.simpleJdbcTemplate.queryForObject(sql, mapper, id);}
請同樣參照Section 11.2.1.2, “JdbcTemplate 的最佳實踐”來獲取一些SimpleJdbcTemplate的最佳實踐
![]() | Note |
---|---|
SimpleJdbcTemplate只是提供了JdbcTemplate所提供的功能的子類。 如果你需要使用JdbcTemplate的方法,而這些方法又沒有在SimpleJdbcTemplate中定義,你需要調(diào)用getJdbcOperations()方法 獲取相應(yīng)的方法調(diào)用。JdbcOperations接口中定義的方法需要在這邊做強制轉(zhuǎn)化才能使用。 |
為了從數(shù)據(jù)庫中取得數(shù)據(jù),我們首先需要獲取一個數(shù)據(jù)庫連接。Spring通過DataSource對象來完成這個工作。 DataSource是JDBC規(guī)范的一部分,它被視為一個通用的數(shù)據(jù)庫連接工廠。通過使用DataSource, Container或Framework可以將連接池以及事務(wù)管理的細(xì)節(jié)從應(yīng)用代碼中分離出來。 作為一個開發(fā)人員,在開發(fā)和測試產(chǎn)品的過程中,你可能需要知道連接數(shù)據(jù)庫的細(xì)節(jié)。但在產(chǎn)品實施時,你不需要知道這些細(xì)節(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("");
SQLExceptionTranslator是一個接口,如果你需要在 SQLException和org.springframework.dao.DataAccessException之間作轉(zhuǎn)換,那么必須實現(xiàn)該接口。 轉(zhuǎn)換器類的實現(xiàn)可以采用一般通用的做法(比如使用JDBC的SQLState code),如果為了使轉(zhuǎn)換更準(zhǔn)確,也可以進行定制(比如使用Oracle的error code)。
SQLErrorCodeSQLExceptionTranslator是SQLExceptionTranslator的默認(rèn)實現(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,從而配合當(dāng)前數(shù)據(jù)庫的使用。
SQLErrorCodeSQLExceptionTranslator使用以下的匹配規(guī)則:
首先檢查是否存在完成定制轉(zhuǎn)換的子類實現(xiàn)。通常SQLErrorCodeSQLExceptionTranslator 這個類可以作為一個具體類使用,不需要進行定制,那么這個規(guī)則將不適用。
接著將SQLException的error code與錯誤代碼集中的error code進行匹配。 默認(rèn)情況下錯誤代碼集將從SQLErrorCodesFactory取得。 錯誤代碼集來自classpath下的sql-error-codes.xml文件,它們將與數(shù)據(jù)庫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將采用該轉(zhuǎn)換器進行轉(zhuǎn)換,而其他的error code將由默認(rèn)的轉(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 SqlUpdateSqlUpdate 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)匀恍枰?使用默認(rèn)的轉(zhuǎn)換器從sql-error-codes.xml中獲取錯誤代碼集。
我們僅需要非常少的代碼就可以達(dá)到執(zhí)行SQL語句的目的,一旦獲得一個 DataSource和一個JdbcTemplate, 我們就可以使用JdbcTemplate提供的豐富功能實現(xiàn)我們的操作。下面的例子使用了極少的代碼完成創(chuàng)建一張表的工作。
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))"); }}
除了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 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結(jié)果 的方法。List中的每一項對應(yīng)查詢返回結(jié)果中的一行。其中最簡單的是queryForList方法, 該方法將返回一個List,該List中的每一條 記錄是一個Map對象,對應(yīng)應(yīng)數(shù)據(jù)庫中某一行;而該Map 中的每一項對應(yīng)該數(shù)據(jù)庫行中的某一列值。下面的代碼片斷接著上面的例子演示了如何用該方法返回表中所有記錄:
private JdbcTemplate jdbcTemplate;public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource);}public List getList() { return this.jdbcTemplate.queryForList("select * from mytable");}
返回的結(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 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)}); }}
JdbcTemplate中有一個update方法,可以方便地從數(shù)據(jù)庫中獲取數(shù)據(jù)庫自動創(chuàng)建的主鍵。(這是JDBC 3.0的標(biāo)準(zhǔn) - 可以參見13.6節(jié)獲取詳細(xì)信息)。 這個方法使用了PreparedStatementCreator接口作為第一個參數(shù), 可以通過這個接口的實現(xiàn)類來定義相應(yīng)的Insert語句。另外一個參數(shù)是KeyHolder, 一旦update方法成功,這個參數(shù)將包含生成的主鍵。這里對于創(chuàng)建合適的PreparedStatement并沒有一個統(tǒng)一的標(biāo)準(zhǔn)。(這也解釋了函數(shù)簽名如此定義的原因)。下面是一個在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 key
DataSourceUtils作為一個幫助類提供易用且強大的數(shù)據(jù)庫訪問能力, 我們可以使用該類提供的靜態(tài)方法從JNDI獲取數(shù)據(jù)庫連接以及在必要的時候關(guān)閉之。 它提供支持線程綁定的數(shù)據(jù)庫連接(比如使用DataSourceTransactionManager的時候,將把數(shù)據(jù)庫連接綁定到當(dāng)前的線程上)。
SmartDataSource是DataSource 接口的一個擴展,用來提供數(shù)據(jù)庫連接。使用該接口的類在指定的操作之后可以檢查是否需要關(guān)閉連接。該接口在某些情況下非常有用,比如有些情況需要重用數(shù)據(jù)庫連接。
AbstractDataSource是一個實現(xiàn)了DataSource 接口的abstract基類。它實現(xiàn)了DataSource接口的 一些無關(guān)痛癢的方法,如果你需要實現(xiàn)自己的DataSource,那么可以繼承該類。
SingleConnectionDataSource是SmartDataSource接口 的一個實現(xiàn),其內(nèi)部包裝了一個單連接。該連接在使用之后將不會關(guān)閉,很顯然它不能在多線程的環(huán)境下使用。
當(dāng)客戶端代碼調(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ù)庫連接,從而避免每次建立物理連接的開銷。
DriverManagerDataSource類實現(xiàn)了 SmartDataSource接口??梢允褂胋ean properties來設(shè)置JDBC Driver屬性,該類每次返回的都是一個新的連接。
該類主要在測試以及脫離J2EE容器的獨立環(huán)境中使用。它既可以用來在application context中作為一個DataSource bean,也可以在簡單的JNDI環(huán)境下使用。 由于Connection.close()僅僅只是簡單的關(guān)閉數(shù)據(jù)庫連接,因此任何能夠獲取DataSource的持久化代碼都能很好的工作。不過使用JavaBean風(fēng)格的連接池 (比如commons-dbcp)也并非難事。即使是在測試環(huán)境下,使用連接池也是一種比使用DriverManagerDataSource更好的做法。
TransactionAwareDataSourceProxy作為目標(biāo)DataSource的一個代理, 在對目標(biāo)DataSource包裝的同時,還增加了Spring的事務(wù)管理能力, 在這一點上,這個類的功能非常像J2EE服務(wù)器所提供的事務(wù)化的JNDI DataSource。
![]() | Note |
---|---|
該類幾乎很少被用到,除非現(xiàn)有代碼在被調(diào)用的時候需要一個標(biāo)準(zhǔn)的 JDBC DataSource接口實現(xiàn)作為參數(shù)。 這種情況下,這個類可以使現(xiàn)有代碼參與Spring的事務(wù)管理。通常最好的做法是使用更高層的抽象 來對數(shù)據(jù)源進行管理,比如JdbcTemplate和DataSourceUtils等等。 |
如果需要更詳細(xì)的資料,請參考 TransactionAwareDataSourceProxy JavaDocs。
DataSourceTransactionManager類是 PlatformTransactionManager接口的一個實現(xiàn),用于處理單JDBC數(shù)據(jù)源。 它將從指定DataSource取得的JDBC連接綁定到當(dāng)前線程,因此它也支持了每個數(shù)據(jù)源對應(yīng)到一個線程。
我們推薦在應(yīng)用代碼中使用DataSourceUtils.getConnection(DataSource)來獲取 JDBC連接,而不是使用J2EE標(biāo)準(zhǔn)的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不支持隔離級別的定制!
有時我們需要執(zhí)行特殊的,由特定廠商提供的與標(biāo)準(zhǔn)JDBC的API不同的JDBC方法。此時,當(dāng)我們在某個應(yīng)用服務(wù)器上運行包裝了這些廠商各自實現(xiàn)的Connection, Statement和ResultSet對象的DataSource 時,可能會遇到一些問題。如果你要訪問這些對象,你可以配置一個包含NativeJdbcExtractor的JdbcTemplate或者OracleLobHandler。
NativeJdbcExtractor根據(jù)執(zhí)行環(huán)境的不同,會有不同的風(fēng)格的實現(xiàn):
SimpleNativeJdbcExtractor
C3P0NativeJdbcExtractor
CommonsDbcpNativeJdbcExtractor
JBossNativeJdbcExtractor
WebLogicNativeJdbcExtractor
WebSphereNativeJdbcExtractor
XAPoolNativeJdbcExtractor
通常來說SimpleNativeJdbcExtractor類對于絕大多數(shù)環(huán)境,已經(jīng)足以屏蔽 Connection 對象??梢詤⒁奐ava Docs獲取詳細(xì)信息。
絕大多數(shù)JDBC驅(qū)動針對批量調(diào)用相同的prepared statement對象提供了性能提升。通過將這些更新操作封裝到一個批量操作中,可以大量減少與數(shù)據(jù)庫的操作頻繁度。 本章節(jié)將詳細(xì)描述使用JdbcTemplate或者SimpleJdbcTemplate進行批量操作的流程。
JdbcTemplate的批量操作特性需要實現(xiàn)特定的接口BatchPreparedStatementSetter來進行的, 通過實現(xiàn)這個接口,并將其傳入batchUpdate方法進行調(diào)用。 這個接口有兩個方法需要實現(xiàn)。一個叫做getBatchSize來提供當(dāng)前需要批量操作的數(shù)量。另外一個方法是setValues 允許你為prepared statement設(shè)置參數(shù)。這個方法將在整個過程中被調(diào)用的次數(shù),則取決于你在getBatchSize中所指定的大小。 下面的示例展示了根據(jù)傳入的list參數(shù)更新actor表,而傳入的list同時作為批量操作的參數(shù)。
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}
如果你是通過讀取文件進行批量操作,那么你可能需要一個特定的批量操作的數(shù)量,不過最后一次的批量操作,你可能沒有那么多數(shù)量的記錄。 在這種情況下,你可以實現(xiàn)InterruptibleBatchPreparedStatementSetter接口,從而允許你在某些情況中斷批量操作,isBatchExhausted 方法允許你指定一個終止批量操作的信號量。
SimpleJdbcTemplate類提供了另外一種批量操作的方式。無需實現(xiàn)一個特定的接口,你只需要提供所有在調(diào)用過程中要用到的參數(shù),框架會遍歷這些參數(shù)值,并使用內(nèi)置的prepared statement類進行批量操作。API將根據(jù)你是否使用命名參數(shù)而有所不同。對于使用命名參數(shù)的情況,你需要提供一個SqlParameterSource的數(shù)組, 其中的每個元素將將作為批量操作的參數(shù)。 你可以使用SqlParameterSource.createBatch方法,通過傳入一個JavaBean的數(shù)組或者一個包含了參數(shù)鍵值對的Map數(shù)組來創(chuàng)建這個數(shù)組。
下面的示例展示了使用命名參數(shù)進行批量更新的方法:
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}
對于使用傳統(tǒng)的“?”作為參數(shù)占位符的情況,你可以傳入一個List,包含了所有需要進行批量更新的對象。這樣的對象數(shù)組必須與每個SQL Statement的占位符以及他們在SQL Statement中出現(xiàn)的位置一一對應(yīng)。
下面是同樣的例子,使用的傳統(tǒng)的“?”作為參數(shù)占位符:
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的數(shù)組,表示在整個操作過程中有多少記錄被批量更新。 這個數(shù)量是由JDBC驅(qū)動所返回的,有時這個返回并不可靠,尤其對于某些JDBC驅(qū)動只是簡單的返回-2作為返回值。
SimpleJdbcInsert類和SimpleJdbcCall類主要利用了JDBC驅(qū)動所提供的數(shù)據(jù)庫元數(shù)據(jù)的一些特性來簡化數(shù)據(jù)庫操作配置。 這意味著你可以盡可能的簡化你的數(shù)據(jù)庫操作配置。當(dāng)然,你可以可以將元數(shù)據(jù)處理的特性關(guān)閉,從而在你的代碼中詳細(xì)指定這些特性。
讓我們從SimpleJdbcInsert類開始。我們將盡可能使用最少量的配置。SimpleJdbcInsert類必須在數(shù)據(jù)訪問層的初始化方法中被初始化。 在這個例子中,初始化方法為setDataSource方法。你無需繼承自SimpleJdbcInsert 類,只需要創(chuàng)建一個新的實例,并通過withTableName方法設(shè)置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 作為它唯一的參數(shù)。 在這里需要重點注意的是,在Map中的所有的key,必須和數(shù)據(jù)庫中定義的列名完全匹配。這是因為我們是通過讀取元數(shù)據(jù)來構(gòu)造實際的Insert語句的。
接下來,我們對于同樣的插入語句,我們并不傳入id,而是通過數(shù)據(jù)庫自動獲取主鍵的方式來創(chuàng)建新的Actor對象并插入數(shù)據(jù)庫。 當(dāng)我們創(chuàng)建SimpleJdbcInsert實例時, 我們不僅需要指定表名,同時我們通過usingGeneratedKeyColumns方法指定需要數(shù)據(jù)庫自動生成主鍵的列名。
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}
這樣我們可以看到與之前執(zhí)行的insert操作的不同之處在于,我們無需添加id到參數(shù)的Map中去,只需要調(diào)用executeReturningKey方法。 這個方法將返回一個java.lang.Number對象,我們可以使用這個對象來創(chuàng)建一個字符類型的實例作為我們的域模型的屬性。 有一點很重要的地方需要注意,我們無法依賴所有的數(shù)據(jù)庫來返回我們指定的Java類型,因為我們只知道這些對象的基類是java.lang.Number。 如果你有聯(lián)合主鍵或者那些非數(shù)字類型的主鍵需要生成,那么你可以使用executeReturningKeyHolder方法返回的KeyHolder對象。
通過指定所使用的字段名稱,可以使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}
執(zhí)行這樣的insert語句所使用的字段,與之前我們所依賴的數(shù)據(jù)庫元數(shù)據(jù)是一致的。
使用Map來指定參數(shù)值有時候工作得非常好,但是這并不是最簡單的使用方式。Spring提供了一些其他的SqlParameterSource實現(xiàn)類來指定參數(shù)值。 我們首先可以看看BeanPropertySqlParameterSource類,這是一個非常簡便的指定參數(shù)的實現(xiàn)類,只要你有一個符合JavaBean規(guī)范的類就行了。它將使用其中的getter方法來獲取參數(shù)值。 下面是一個例子:
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}
另外一個實現(xiàn)類:MapSqlParameterSource也使用Map來指定參數(shù),但是他另外提供了一個非常簡便的addValue方法,可以被連續(xù)調(diào)用,來增加參數(shù)。
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}
正如你看到的,配置是一樣的,區(qū)別只是切換了不同的提供參數(shù)的實現(xiàn)方式來執(zhí)行調(diào)用。
接下來我們把我們的關(guān)注點放在使用 SimpleJdbcCall 來進行存儲過程的調(diào)用上。 設(shè)計這個類的目的在于使得調(diào)用存儲過程盡可能簡單。它同樣利用的數(shù)據(jù)庫元數(shù)據(jù)的特性來查找傳入的參數(shù)和返回值。 這意味著你無需明確聲明那些參數(shù)。當(dāng)然,如果你喜歡,可以依然聲明這些參數(shù),尤其對于某些參數(shù),你無法直接將他們映射到Java類上,例如ARRAY類型和STRUCT類型的參數(shù)。 在我們的第一個示例中,我們可以看到一個簡單的存儲過程調(diào)用,它僅僅返回VARCHAR和DATE類型。 這里,我特地為Actor類增加了一個birthDate的屬性,從而可以使得返回值擁有不同的數(shù)據(jù)類型。 這個存儲過程讀取actor的主鍵,并返回first_name,last_name,和birth_date字段作為返回值。 下面是這個存儲過程的源碼,它可以工作在MySQL數(shù)據(jù)庫上:
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;
正如你看到的,這里有四個參數(shù),其中一個是傳入的參數(shù)“in_id”,表示了Actor的主鍵,剩下的參數(shù)是作為讀取數(shù)據(jù)庫表中的數(shù)據(jù)所返回的返回值。
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執(zhí)行存儲過程需要創(chuàng)建一個SqlParameterSource的實現(xiàn)類來指定傳入的參數(shù)。 需要注意的是,傳入?yún)?shù)的名稱與存儲過程中定義的名稱必須保持一致。這里,我們無需保持一致,因為我們使用數(shù)據(jù)庫的元數(shù)據(jù)信息來決定我們需要什么樣的數(shù)據(jù)庫對象。 當(dāng)然,你在源代碼中所指定的名稱可能和數(shù)據(jù)庫中完全不同,有的數(shù)據(jù)庫會把這些名稱全部轉(zhuǎn)化成大寫,而有些數(shù)據(jù)庫會把這些名稱轉(zhuǎn)化為小寫。
execute方法接收傳入的參數(shù),并返回一個Map作為返回值,這個Map包含所有在存儲過程中指定的參數(shù)名稱作為key。 在這個例子中,他們分別是out_first_name,out_last_name和 out_birth_date。
execute方法的最后部分是使用存儲過程所返回的值創(chuàng)建一個新的Actor實例。 同樣的,這里我們將名稱與存儲過程中定義的名稱保持一致非常重要。在這個例子中,在返回的Map中所定義的key值和數(shù)據(jù)庫的存儲過程中定義的值一致。 你可能需要在這里指定Spring使用Jakarta Commons提供的CaseInsensitiveMap。這樣做,你需要在創(chuàng)建你自己的JdbcTemplate類時,設(shè)置setResultsMapCaseInsensitive屬性為True。 然后,你將這個自定義的JdbcTemplate傳入SimpleJdbcCall的構(gòu)造函數(shù)。當(dāng)然,你需要把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}
通過這樣的配置,你就可以無需擔(dān)心返回參數(shù)值的大小寫問題。
你已經(jīng)看到如何通過元數(shù)據(jù)來簡化參數(shù)配置,但是你也可以明確地指定這些參數(shù)??梢栽趧?chuàng)建SimpleJdbcCall時,通過使用declareParameters方法來聲明參數(shù)。 這個方法接收一組SqlParameter對象作為參數(shù)。我們可以參照下一個章節(jié),如何創(chuàng)建SqlParameter。
我們可以有選擇性的顯示聲明一個、多個、甚至所有的參數(shù)。參數(shù)元數(shù)據(jù)在這里會被同時使用。 通過調(diào)用withoutProcedureColumnMetaDataAccess方法,我們可以指定數(shù)據(jù)庫忽略所有的元數(shù)據(jù)處理并使用顯示聲明的參數(shù)。 另外一種情況是,其中的某些參數(shù)值具有默認(rèn)的返回值,我們需要在返回值中指定這些返回值。為了實現(xiàn)這個特性,我們可以使用useInParameterNames來指定一組需要被包含的參數(shù)名稱。
這是一個完整的聲明存儲過程調(diào)用的例子:
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}
執(zhí)行和最終的返回處理是相同的,我們在這里只是明確聲明了參數(shù)類型,而不是依賴數(shù)據(jù)庫元數(shù)據(jù)特性。 這一點很重要,尤其對于那些數(shù)據(jù)庫并不支持元數(shù)據(jù)的情況。當(dāng)前,我們支持元數(shù)據(jù)的特性的數(shù)據(jù)包含:Apache Derby、DB2、MySQL、 Microsoft SQL Server、Oracle和Sybase。我們同時對某些數(shù)據(jù)庫內(nèi)置函數(shù)支持元數(shù)據(jù)特性:MySQL、Microsoft SQL Server和Oracle。
為SimpleJdbc類或者后續(xù)章節(jié)提到的RDBMS操作指定參數(shù),你需要使用SqlParameter或者他的子類。 你可以通過指定參數(shù)名稱以及對應(yīng)的SQL類型并傳入構(gòu)造函數(shù)作為參數(shù)來指定SqlParameter,其中,SQL類型是java.sql.Types中所定義的常量。 我們已經(jīng)看到過類似的聲明:
new SqlParameter("in_id", Types.NUMERIC), new SqlOutParameter("out_first_name", Types.VARCHAR),
第一行的SqlParameter定義了一個傳入?yún)?shù)。傳入?yún)?shù)可以在所有的存儲過程中使用,也可以在稍后章節(jié)中提到的SqlQuery類及其子類中使用。
第二行SqlOutParameter定義了一個返回值。它可以被存儲過程調(diào)用所使用。當(dāng)然,還有一個SqlInOutParameter類,可以用于輸入輸出參數(shù)。 也就是說,它既是一個傳入?yún)?shù),也是一個返回值。
除了參數(shù)名稱和SQL類型,你還可以聲明一些其他額外的選項。對于傳入?yún)?shù),你可以為numeric數(shù)據(jù)類型指定精度,或者對于特定的數(shù)據(jù)庫指定特殊類型。 對于返回值,你可以提供一個RowMapper接口來處理所有從REF cursor返回的列。另外一個選項是指定一個SqlReturnType類,從而可以定制返回值的處理方式。
內(nèi)置函數(shù)的調(diào)用幾乎和存儲過程的調(diào)用是一樣的。唯一的不同在于,你需要聲明的是一個函數(shù)的名稱而不是存儲過程的名稱。 這可以通過withFunctionName方法來完成。使用這個方法,表明你的調(diào)用是一個函數(shù)。你所指定的這個函數(shù)名稱將被作為調(diào)用對象。 同時有一個叫做executeFunction的方法,將獲得特定的Java類型的函數(shù)調(diào)用的返回值。 此時,你無需通過返回的Map來獲取返回值。另外有一個類似的便捷方法executeObject用于存儲過程,但是他只能處理單個返回值的情況。 下面的示例展示了一個叫做get_actor_name 的函數(shù)調(diào)用,返回actor的完整的名稱。 這個函數(shù)將工作在MySQL數(shù)據(jù)庫上。
CREATE FUNCTION get_actor_name (in_id INTEGER)RETURNS VARCHAR(200) READS SQL DATA BEGIN DECLARE out_name VARCHAR(200); SELECT concat(first_name, ' ', last_name) INTO out_name FROM t_actor where id = in_id; RETURN out_name;END;
調(diào)用這個函數(shù),我們依然在初始化方法中創(chuàng)建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}
被調(diào)用的函數(shù)返回一個String類型。
期望通過調(diào)用存儲過程或者函數(shù)來返回ResultSet一直是一個問題。一些數(shù)據(jù)庫在JDBC結(jié)果處理中返回結(jié)果集,而另外一些數(shù)據(jù)庫則需要明確指定返回值的類型。 無論哪種方法,都需要在循環(huán)遍歷結(jié)果集時,做出一些額外的工作,從而處理每一條記錄。 通過SimpleJdbcCall,你可以使用returningResultSet方法,并定義一個RowMapper的實現(xiàn)類來處理特定的返回值。 當(dāng)結(jié)果集在返回結(jié)果處理過程中沒有被定義名稱時,返回的結(jié)果集必須與定義的RowMapper的實現(xiàn)類指定的順序保持一致。 而指定的名字也會被用作返回結(jié)果集中的名稱。
在這個例子中,我們將使用一個存儲過程,它并不接收任何參數(shù),返回t_actor表中的所有的行,下面是MySQL數(shù)據(jù)庫中的存儲過程源碼:
CREATE PROCEDURE read_all_actors()BEGIN SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;END;
要調(diào)用這個存儲過程,我們需要定義一個RowMapper的實現(xiàn)類。我們所使用的類遵循JavaBean的規(guī)范,所以我們可以使用ParameterizedBeanPropertyRowMapper作為實現(xiàn)類。 通過將相應(yīng)的class類作為參數(shù)傳入到newInstance方法中,我們可以創(chuàng)建這個實現(xiàn)類。
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}
這個函數(shù)調(diào)用傳入一個空的Map進入,因為這里不需要任何的參數(shù)傳入。而函數(shù)調(diào)用所返回的結(jié)果集將返回的是Actors列表。
org.springframework.jdbc.object包下的類允許用戶以更加 面向?qū)ο蟮姆绞饺ピL問數(shù)據(jù)庫。比如說,用戶可以執(zhí)行查詢并返回一個list, 該list作為一個結(jié)果集將把從數(shù)據(jù)庫中取出的列數(shù)據(jù)映射到業(yè)務(wù)對象的屬性上。 用戶也可以執(zhí)行存儲過程,以及運行更新、刪除以及插入SQL語句。
![]() | Note |
---|---|
在許多Spring開發(fā)人員中間存在有一種觀點,那就是下面將要提到的各種RDBMS操作類 (StoredProcedure類除外) 通常也可以直接使用JdbcTemplate相關(guān)的方法來替換。 相對于把一個查詢操作封裝成一個類而言,直接調(diào)用JdbcTemplate方法將更簡單而且更容易理解。 必須強調(diào)的一點是,這僅僅只是一種觀點而已, 如果你認(rèn)為你可以從直接使用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對象。 下面這個例子演示了一個定制查詢,它將從客戶表中取得的數(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ù)編譯。
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游標(biāo)類型的輸出參數(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中的每一行映射為一個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")); }}
如果你需要給存儲過程傳輸入?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); 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); }}
SqlFunction RDBMS操作類封裝了一個SQL“函數(shù)”包裝器(wrapper), 該包裝器適用于查詢并返回一個單行結(jié)果集。默認(rèn)返回的是一個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();}
在Spring的JDBC框架的所有工作模式中貫徹了一些與參數(shù)和數(shù)據(jù)處理相關(guān)的基本原則。
多數(shù)情況下,Spring會根據(jù)傳入的參數(shù)值來設(shè)定相應(yīng)的SQL類型。有時,我們有必要明確指定傳入?yún)?shù)所代表的SQL類型,這一點對于正確設(shè)置NULL值的時候可能比較有用。
另外還有一些其他的不同方面的作用:
多數(shù)JdbcTemplate的update或者query方法會接收一個額外的int數(shù)組構(gòu)成的參數(shù)。 這個數(shù)組需要提供的是使用java.sql.Types中所定義的SQL類型。而這個數(shù)組中定義的類型需要與每個傳入的參數(shù)所對應(yīng)。
你可以使用SqlParameterValue對參數(shù)進行額外的封裝從而包裝更多的參數(shù)信息。通過傳入?yún)?shù)值和對應(yīng)的SQL類型作為構(gòu)造函數(shù)的參數(shù),你可以創(chuàng)建這個類的一個實例。 你也可以為numeric的值提供一些額外的精度要求。
對于那些使用命名參數(shù)的情況,你可以使用SqlParameterSource、BeanPropertySqlParameterSource或者MapSqlParameterSource類。 他們都具備了為命名參數(shù)注冊SQL類型的功能。
你可以在數(shù)據(jù)庫中存儲圖像、二進制對象或者大文本等對象。這些較大的二進制對象被稱之為BLOB類型,而對應(yīng)的大文本對象被稱之為CLOB對象。 Spring允許你使用JdbcTemplate、更高層次封裝的RDBMS對象和SimpleJdbc類對這些大對象進行操作。 所有的這些操作方式都實現(xiàn)了LobHandler接口來處理LOB類型的數(shù)據(jù)。 LobHandler接口提供了訪問LobCreator的方法,通過調(diào)用getLobCreator,你可以創(chuàng)建一個新的LOB類型的數(shù)據(jù)。
LobCreator/LobHandler接口針對LOB類型的數(shù)據(jù)操作提供了下列支持:
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
現(xiàn)在我們通過一個示例來展示如何創(chuàng)建一個BLOB數(shù)據(jù)并插入數(shù)據(jù)庫。稍后的例子,我們將展示如何從數(shù)據(jù)庫中將BLOB數(shù)據(jù)讀取出來。
這個例子使用JdbcTemplate和一個AbstractLobCreatingPreparedStatementCallback的實現(xiàn)類。 這里唯一需要實現(xiàn)的方法就是"setValues"。在這個方法中,將提供一個LobCreator接口,被用作在你的插入語句中設(shè)置LOB字段的值。
我們假設(shè)有一個變量叫做“l(fā)obHandler”已經(jīng)被設(shè)置到DefaultLobHandler的實例中。當(dāng)然,這是由注入完成的。
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實現(xiàn)類是一個普通的DefaultLobHandler |
![]() | 使用setClobAsCharacterStream,我們傳入CLOB的內(nèi)容 |
![]() | 使用setBlobAsBinartStream,我們傳入BLOB的內(nèi)容 |
現(xiàn)在我們來示范從數(shù)據(jù)庫中讀取LOB數(shù)據(jù)。我們這里再次使用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內(nèi)容 |
![]() | 使用getBlobAsBytes獲取BLOB內(nèi)容 |
SQL標(biāo)準(zhǔn)允許基于一個帶參數(shù)列表的表達(dá)式進行查詢。一個典型的例子可能像這樣:"select * from T_ACTOR where id in (1, 2, 3)"。 不過這種參數(shù)列表的方式并不能直接被JDBC標(biāo)準(zhǔn)所支持 - 因為并不存在這種聲明一個列表參數(shù)作為占位符的方式。 你不得不為此寫多個占位符來表示多個參數(shù),或者當(dāng)你知道占位符的數(shù)量時,你可以動態(tài)構(gòu)建SQL字符串。 NamedParameterJdbcTemplate和SimpleJdbcTemplate中所提供的命名參數(shù)的特性,采用的是后面一種做法。 當(dāng)你傳入?yún)?shù)時,你需要傳入一個java.util.List類型,支持基本類型。而這個list將會在SQL執(zhí)行時替換占位符并傳入?yún)?shù)。
![]() | Note |
---|---|
在使用IN語句時,當(dāng)你傳入大批量的值時要小心,JDBC標(biāo)準(zhǔn)并不確保超過100個元素在IN語句中。 有不少數(shù)據(jù)庫可以超出這個值的限制,但是不同的數(shù)據(jù)庫會有不同的數(shù)量限制,比如Oracle的限制數(shù)量是1000個。 |
除了基本類型之外,你還可以創(chuàng)建一個java.util.List的對象數(shù)組,這可以讓你支持在IN表達(dá)式中編寫多重表達(dá)式,例如"select * from T_ACTOR where (id, last_name) in ((1, 'Johnson'), (2, 'Harrop'))". 當(dāng)然,這樣做的前提是數(shù)據(jù)庫底層的語法支持。
當(dāng)調(diào)用存儲過程時,有時需要使用數(shù)據(jù)庫特定的復(fù)雜類型。為了適應(yīng)這些類型,Spring提供了SqlReturnType類來處理存儲過程的返回值,而使用SqlTypeValue來處理傳入的參數(shù)。
下面這個例子,將Oracle的STRUCT對象作為返回值,這是一個由用戶自定義的“ITEM_TYPE”。 SqlReturnType接口有唯一的方法“getTypeValue”需要被實現(xiàn)。而這個接口的實現(xiàn)將被用作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代碼調(diào)用存儲過程使用SqlTypeValue來傳入一個TestItem作為參數(shù)。 SqlTypeValue接口有一個方法"createTypeValue"需要被實現(xiàn)。 一個活動的數(shù)據(jù)庫連接也同時被傳入,它將被用作創(chuàng)建數(shù)據(jù)庫特定的對象,類似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 現(xiàn)在可以被加入到作為參數(shù)的Map中去,從而可以執(zhí)行相應(yīng)的存儲過程。