http://blog.csdn.net/sunsnow8/archive/2005/01/10/246588.aspx
oracle 對於進階特性總是與眾不同(我極力討厭這一點,如果使用它的產品就要對這種產品
進行特定的編程,這也是我從不看好weblogic之類的平台的原因),大對象存取一要定用它自己
的LOB對象,所幸我還能通過LONG RAW來代替.以便能使程式不需要特定的編碼.但對於預存程序
(我是說返回結果集的預存程序),我還沒有什麼方法能用一個通用的程式來處理ORACLE.太多的
教材或文章在講預存程序的調用只是不負責任地簡單執行一些涵數或無結果返回的預存程序,
使大多數讀者根本不知道到底如何調用預存程序的結果集.而在Spring中,根本就沒有真正完全
地介紹對預存程序的結果集的處理,更別說象oracle這種"特別"的預存程序的結果集處理.
先來簡單看一下我們如何在JDBC中處理預存程序的結果集的通用流程:
1.擷取CallableStatement語句:
CallableStatement cs = conn.prepareCall("{call spName(?,?,?)}");
2.傳入輸入參數和註冊輸出參數
cs.setXXX(index,value);//輸入參數
cs.registerOutParameter(index,type);//輸出參數
3.執行預存程序:
cs.execute();
對於一個存諸過程,如果返回的結果是我們預Crowdsourced Security Testing道的,那麼可以根據預存程序定義的順序來進行
處理(事實上一般不會這樣),但對於複雜的多結果集的處理,如何定義一個通用的流程?
我們先看一下JDBC本身能為我們提供什麼?
一個預存程序執行後返回的是boolean型:
boolean flag = callableStatement.execute();
如果flag為true,那麼說明返回了一個結果集(ResultSet)類型,你可以用getResultSet()得到當前行所在
的結果,而如果返回為flase,說明什麼呢?
如果你不進行處理,什麼也不能說明,只能說明當前指標不是ResultSet,有可能是更新計數(updateCount)
也可能什麼也沒有反因.
那麼如果當前指標為flase時如何處理?我們應該先getUpdateCount();如果返回-1,既不是結果集,又
不是更新計數了.說明沒的返回了.而如果getUpdateCount()返回0或大於0,則說明當前指標是更新計數(
0的時候有可能是DDL指令).無論是返回結果集或是更新計數,那麼則可能還繼續有其它返回.只有在當前
指指標getResultSet()==null && getUpdateCount() == -1才說明沒有再多的返回.
預存程序的返回和ResultSet類似,每次處理的返回結果相當於是ResultSet的Row,只不過預存程序的Row
最先在第一行而不是象ResultSet要先next才到第一行,預存程序向下移動一行用getMoreResults(),相
當於ResultSet的next().同樣它返回boolean和上面的flag一樣,只是說明當前行是不是ResultSet,如果是
flase,你還是要判斷是不是updateCount,在每一行,都要先同時判斷是否為ResultSet還是UpdateCount,如
果是其中一種則要繼續getMoreResults(),當不是ResultSet也不是updateCount時,說明沒有返回結果了,
這時再擷取輸出參數.
看明白了嗎?
那我們就根據上面的規則來寫一個通用的流程吧:
首先,我們要確定什麼時候沒說結果集了:
if(cs.getResultSet() == null && cs.getUpdateCount() == -1)
現在我們做一個迴圈:
ResultSet rs = null;
int updateCount = -1;
flag = cs.execute();
do{
updateCount = cs.getUpdateCount();
if(updateCount != -1){//說明當前行是一個更新計數
//處理.
cs.getMoreResults();
continue;//已經是更新計數了,處理完成後應該移動到下一行
//不再判斷是否是ResultSet
}
rs = cs.getResultSet();
if(rs != null){//如果到了這裡,說明updateCount == -1
//處理rs
cs.getMoreResults();
continue;
//是結果集,處理完成後應該移動到下一行
}
//如果到了這裡,說明updateCount == -1 && rs == null,什麼也沒的了
}while(!(updateCount == -1 && rs == null));
cs.getXXX(int);//擷取輸出參數
以上是對於通用的預存程序返回的結果集的處理,而oracle,它不能返回結果集,只能在輸出參數中
返回一個cursor,所以通用的流程中你擷取不到任何結果:
package PK_AREA_PUBLIC is
TYPE serarch_result IS REF CURSOR;
PROCEDURE area_search(vTarget_in IN VARCHAR2 ,cur_result_out OUT serarch_result) ;
end PK_AREA_PUBLIC;
package body PK_AREA_PUBLIC is
PROCEDURE area_search(vTarget_in IN VARCHAR2 ,cur_result_out OUT serarch_result)
IS
sqlstr VARCHAR2(1000);
BEGIN
sqlstr:='select .................................';
OPEN cur_result_out FOR sqlstr USING vTarget_in;
END area_search;
end PK_AREA_PUBLIC;
對於上面的例子,預存程序有一個輸入參數,一個輸出參數,我們要接受輸出參數作為結果集處理.所
以註冊的時候應該註冊為:
cs.registerOutParameter(2,oracle.jdbc.OracleTypes.CURSOR);//輸出參數
這樣在預存程序執行後,擷取輸出數造型為ResultSet就可以處理:
ResultSet rs = (ResultSet)cs.getObject(2);
如果有多個結果集就用多個輸出參數.
明白了ORACLE的特殊性,我們再看看在spring中如果處理它的預存程序的結果集:
spring在處理複雜物件的時候,大都採用回調的方法,要求程式員自己實現介面方法.也就是它提供了
程式運行時的參數,要求你自己對這些參數進行處理.對於JdbcTemplate,它在很多地方提供了ResultSet
參數供程式員處理.在Spring文檔中提供了對於通用流程,也就是從預存程序執行結果中擷取結果集的
常式:
Map out = execute(new HashMap());
其實它是預設實現了上面JDBC通用流程中對ResuleSet到Map的封裝.而對於Oracle,我們就必須自己手
實現對輸出參數中ResultSet的回調:
public class SpringStoredProcedure
extends StoredProcedure {
public ArrayList<HashMap> set = new ArrayList<HashMap>();
//聲明一個用於接收結果集的資料結構,其中的元素為row,用map存放
private Map inParam;//輸入參數
private RowMapper rm = new RowMapper(){
public Object mapRow(ResultSet rs,int rowNum) throws SQLException{
return null;//不用從預存程序本身擷取結果
}
};
private RowMapperResultReader callback = new RowMapperResultReader(rm ){
public void processRow(ResultSet rs) //回調處理
throws SQLException{
int count = rs.getMetaData().getColumnCount();
String[] header = new String[count];
for(int i=0;i<count;i++)
header[i] = rs.getMetaData().getColumnName(i+1);
while(rs.next()){
HashMap<String,String> row = new HashMap(count+7);
for(int i=0;i<count;i++)
row.put(header[i],rs.getString(i+1));
set.add(row);
}
}
}; //RowMapperResultReader作為輸出參數的回調控制代碼
public SpringStoredProcedure(DataSource ds, String SQL) {
setDataSource(ds);
setSql(SQL);
}
public void setOutParameter(String column,int type){
declareParameter(new SqlOutParameter(column, type,callback));
//利用回調控制代碼註冊輸出參數
}
public void setParameter(String column,int type){
declareParameter(new SqlParameter(column, type));
}
public void SetInParam(Map inParam){
this.inParam = inParam;
}
public Map execute() {
compile();
return execute(this.inParam);
}
}
下面我們看一下調用過程:
DriverManagerDataSource ds = .......;
SpringStoredProcedure sp = new SpringStoredProcedure(ds,"PK_AREA_PUBLIC.area_search");
//註冊參數類型,輸入參數和輸出參數同時註冊,否則不能正確編譯預存程序
sp.setParameter("vTarget_in",java.sql.Types.VARCHAR);
sp.setOutParameter("cur_result_out",oracle.jdbc.OracleTypes.CURSOR);
sp.compile();
//傳入輸入參數值
Map in = new HashMap();
in.put("vTarget_in","一個內容");
sp.SetInParam(in);
//執行預存程序
sp.execute();
Map m = sp.set.get(0);//ReultSet的第一條記錄
//set定義為SpringStoredProcedure的屬性用於接收回調時的資料
//如果有多個輸出參數,應該在每個輸出參數的回調方法中產生該輸出
//參數對應的ArrayList,然後加到一個成員變數的資料結構中.
Iterator i = m.keySet().iterator();
while(i.hasNext()){
String key = i.next().toString();
System.out.println(key + "=>" + m.get(key));
}
總之,上面的方法雖然解決了Spring中對Oracle預存程序的調用,但我極力不推薦這程複雜的處理
著作權聲明:CSDN是本Blog託管服務提供者。如本文牽涉著作權問題,CSDN不承擔相關責任,請著作權擁有者直接與文章作者聯絡解決。