?
This document uses PHP Chinese website manual Release
org.springframework.jdbc.object
包下的類允許用戶以更加
面向?qū)ο蟮姆绞饺ピL問數(shù)據(jù)庫。比如說,用戶可以執(zhí)行查詢并返回一個list,
該list作為一個結(jié)果集將把從數(shù)據(jù)庫中取出的列數(shù)據(jù)映射到業(yè)務(wù)對象的屬性上。
用戶也可以執(zhí)行存儲過程,以及運行更新、刪除以及插入SQL語句。
在許多Spring開發(fā)人員中間存在有一種觀點,那就是下面將要提到的各種RDBMS操作類
(StoredProcedure
類除外)
通常也可以直接使用JdbcTemplate
相關(guān)的方法來替換。
相對于把一個查詢操作封裝成一個類而言,直接調(diào)用JdbcTemplate
方法將更簡單而且更容易理解。
必須強調(diào)的一點是,這僅僅只是一種觀點而已, 如果你認為你可以從直接使用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(); } public int run(int id, int rating) { Object[] params = new Object[] { new Integer(rating), new Integer(id)}; return update(params); } }
StoredProcedure
類是一個抽象基類,它是對RDBMS存儲過程的一種抽象。
該類提供了多種execute(..)
方法,不過這些方法的訪問類型都是protected
的。
從父類繼承的sql
屬性用來指定RDBMS存儲過程的名字。
盡管該類提供了許多必須在JDBC3.0下使用的功能,但是我們更關(guān)注的是JDBC 3.0中引入的命名參數(shù)特性。
下面的程序演示了如何調(diào)用Oracle中的sysdate()
函數(shù)。
這里我們創(chuàng)建了一個繼承StoredProcedure
的子類,雖然它沒有輸入?yún)?shù),
但是我必須通過使用SqlOutParameter
來聲明一個日期類型的輸出參數(shù)。
execute()
方法將返回一個map,map中的每個entry是一個用參數(shù)名作key,以輸出參數(shù)為value的名值對。
import java.sql.Types;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.datasource.*;
import org.springframework.jdbc.object.StoredProcedure;
public class TestStoredProcedure {
public static void main(String[] args) {
TestStoredProcedure t = new TestStoredProcedure();
t.test();
System.out.println("Done!");
}
void test() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("oracle.jdbc.OracleDriver");
ds.setUrl("jdbc:oracle:thin:@localhost:1521:mydb");
ds.setUsername("scott");
ds.setPassword("tiger");
MyStoredProcedure sproc = new MyStoredProcedure(ds);
Map results = sproc.execute();
printMap(results);
}
private class MyStoredProcedure extends StoredProcedure {
private static final String SQL = "sysdate";
public MyStoredProcedure(DataSource ds) {
setDataSource(ds);
setFunction(true);
setSql(SQL);
declareParameter(new SqlOutParameter("date", Types.DATE));
compile();
}
public Map execute() {
// the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
return execute(new HashMap());
}
}
private static void printMap(Map results) {
for (Iterator it = results.entrySet().iterator(); it.hasNext(); ) {
System.out.println(it.next());
}
}
}
下面是StoredProcedure
的另一個例子,它使用了兩個Oracle游標類型的輸出參數(shù)。
import oracle.jdbc.driver.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
public class TitlesAndGenresStoredProcedure extends StoredProcedure {
private static final String SPROC_NAME = "AllTitlesAndGenres";
public TitlesAndGenresStoredProcedure(DataSource dataSource) {
super(dataSource, SPROC_NAME);
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
declareParameter(new SqlOutParameter("genres", OracleTypes.CURSOR, new GenreMapper()));
compile();
}
public Map execute() {
// again, this sproc has no input parameters, so an empty Map is supplied...
return super.execute(new HashMap());
}
}
值得注意的是TitlesAndGenresStoredProcedure
構(gòu)造函數(shù)中
declareParameter(..)
的SqlOutParameter
參數(shù),
該參數(shù)使用了RowMapper
接口的實現(xiàn)。這是一種非常方便而強大的重用方式。
下面我們來看一下RowMapper
的兩個具體實現(xiàn)。
首先是TitleMapper
類,它簡單的把ResultSet
中的每一行映射為一個Title
Domain Object。
import com.foo.sprocs.domain.Title; import org.springframework.jdbc.core.RowMapper; import java.sql.ResultSet; import java.sql.SQLException; public final class TitleMapper implements RowMapper { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { Title title = new Title(); title.setId(rs.getLong("id")); title.setName(rs.getString("name")); return title; } }
另一個是GenreMapper
類,也是非常簡單的將ResultSet
中的每一行映射為一個Genre
Domain Object。
import org.springframework.jdbc.core.RowMapper; import java.sql.ResultSet; import java.sql.SQLException; import com.foo.domain.Genre; public final class GenreMapper implements RowMapper { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { return new Genre(rs.getString("name")); } }
如果你需要給存儲過程傳輸入?yún)?shù)(這些輸入?yún)?shù)是在RDBMS存儲過程中定義好了的),
則需要提供一個指定類型的execute(..)
方法,
該方法將調(diào)用基類的protected
execute(Map parameters)
方法。例如:
import oracle.jdbc.driver.OracleTypes; import org.springframework.jdbc.core.SqlOutParameter; import org.springframework.jdbc.object.StoredProcedure; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; public class TitlesAfterDateStoredProcedure extends StoredProcedure { private static final String SPROC_NAME = "TitlesAfterDate"; private static final String CUTOFF_DATE_PARAM = "cutoffDate"; public TitlesAfterDateStoredProcedure(DataSource dataSource) { super(dataSource, SPROC_NAME); 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é)果集。默認返回的是一個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(); }