存儲過程常用技巧3

3.4 select into不可乎視的問題
我們知道在pl/sql中要想從數據表中向變量賦值,需要使用select into 子句。
但是它會帶動來一些問題,如果查詢沒有記錄時,會拋出no_data_found異常。
如果有多條記錄時,會拋出too_many_rows異常。
這個是比較糟糕的。一旦拋出了異常,就會讓過程中斷。特別是no_data_found這種異常,沒有嚴重到要讓程序中斷的地步,可以完全交給由程序進行處理。

Java代碼 複製代碼
  1. create or replace procedure procexception(p varchar2)   
  2. as    
  3.   v_postype varchar2(20);   
  4. begin   
  5.    select pos_type into v_postype from pos_type_tbl where 1=0;   
  6.     dbms_output.put_line(v_postype);   
  7. end;   
  8.       
create or replace procedure procexception(p varchar2)
as 
  v_postype varchar2(20);
begin
   select pos_type into v_postype from pos_type_tbl where 1=0;
    dbms_output.put_line(v_postype);
end;
	

執行這個過程

Java代碼 複製代碼
  1. SQL> exec procexception('a');   
  2. 報錯   
  3. ORA-01403: no data found   
  4. ORA-06512: at "LIFEMAN.PROCEXCEPTION", line 6  
  5. ORA-06512: at line 1  
SQL> exec procexception('a');
報錯
ORA-01403: no data found
ORA-06512: at "LIFEMAN.PROCEXCEPTION", line 6
ORA-06512: at line 1


處理這個有三個辦法
1. 直接加上異常處理。

Java代碼 複製代碼
  1. create or replace procedure procexception(p varchar2)   
  2. as    
  3.   v_postype varchar2(20);   
  4.      
  5. begin   
  6.    select pos_type into v_postype from pos_type_tbl where 1=0;   
  7.     dbms_output.put_line(v_postype);   
  8. exception    
  9.   when no_data_found then   
  10.     dbms_output.put_line('沒找到數據');   
  11. end;  
create or replace procedure procexception(p varchar2)
as 
  v_postype varchar2(20);
  
begin
   select pos_type into v_postype from pos_type_tbl where 1=0;
    dbms_output.put_line(v_postype);
exception 
  when no_data_found then
    dbms_output.put_line('沒找到數據');
end;

這樣做換湯不換藥,程序仍然被中斷。可能這樣不是我們所想要的。
2. select into做爲一個獨立的塊,在這個塊中進行異常處理

Java代碼 複製代碼
  1. create or replace procedure procexception(p varchar2)   
  2. as    
  3.   v_postype varchar2(20);   
  4.      
  5. begin   
  6.   begin   
  7.    select pos_type into v_postype from pos_type_tbl where 1=0;   
  8.     dbms_output.put_line(v_postype);   
  9.  exception    
  10.   when no_data_found then   
  11.     v_postype := '';   
  12.   end;   
  13.   dbms_output.put_line(v_postype);   
  14. end;  
create or replace procedure procexception(p varchar2)
as 
  v_postype varchar2(20);
  
begin
  begin
   select pos_type into v_postype from pos_type_tbl where 1=0;
    dbms_output.put_line(v_postype);
 exception 
  when no_data_found then
    v_postype := '';
  end;
  dbms_output.put_line(v_postype);
end;

這是一種比較好的處理方式了。不會因爲這個異常而引起程序中斷。
3.使用遊標

Java代碼 複製代碼
  1. create or replace procedure procexception(p varchar2)   
  2. as    
  3.   v_postype varchar2(20);   
  4.   cursor c_postype is select pos_type  from pos_type_tbl where 1=0;   
  5. begin   
  6.   open c_postype;   
  7.     fetch c_postype into v_postype;   
  8.   close c_postype;   
  9.   dbms_output.put_line(v_postype);   
  10. end;  
create or replace procedure procexception(p varchar2)
as 
  v_postype varchar2(20);
  cursor c_postype is select pos_type  from pos_type_tbl where 1=0;
begin
  open c_postype;
    fetch c_postype into v_postype;
  close c_postype;
  dbms_output.put_line(v_postype);
end;

這樣就完全的避免了no_data_found異常。完全交由程序員來進行控制了。

第二種情況是too_many_rows 異常的問題。
Too_many_rows 這個問題比起no_data_found要複雜一些。
給一個變量賦值時,但是查詢結果有多個記錄。
處理這種問題也有兩種情況:
1. 多條數據是可以接受的,也就是說從結果集中隨便取一個值就行。這種情況應該很極端了吧,如果出現這種情況,也說明了程序的嚴謹性存在問題。
2. 多條數據是不可以被接受的,在這種情況肯定是程序的邏輯出了問題,也說是說原來根本就不會想到它會產生多條記錄。
對於第一種情況,就必須採用遊標來處理,而對於第二種情況就必須使用內部塊來處理,重新拋出異常。
多條數據可以接受,隨便取一條,這個跟no_data_found的處理方式一樣,使用遊標。
我這裏僅說第二種情況,不可接受多條數據,但是不要忘了處理no_data_found哦。這就不能使用遊標了,必須使用內部塊。

Java代碼 複製代碼
  1. create or replace procedure procexception2(p varchar2)   
  2. as    
  3.   v_postype varchar2(20);   
  4.     
  5. begin   
  6.   begin   
  7.     select pos_type into v_postype from pos_type_tbl where rownum < 5;   
  8.   exception   
  9.     when no_data_found then   
  10.       v_postype :=null;   
  11.     when too_many_rows then   
  12.       raise_application_error(-20000,'對v_postype賦值時,找到多條數據');   
  13.   end;   
  14.  dbms_output.put_line(v_postype);   
  15. end;  
create or replace procedure procexception2(p varchar2)
as 
  v_postype varchar2(20);
 
begin
  begin
    select pos_type into v_postype from pos_type_tbl where rownum < 5;
  exception
    when no_data_found then
      v_postype :=null;
    when too_many_rows then
      raise_application_error(-20000,'對v_postype賦值時,找到多條數據');
  end;
 dbms_output.put_line(v_postype);
end;

需要注意的是一定要加上對no_data_found的處理,對出現多條記錄的情況則繼續拋出異常,讓上一層來處理。
總之對於select into的語句需要注意這兩種情況了。需要妥當處理啊。

3.5 在存儲過程中返回結果集
我們使用存儲過程都是返回值都是單一的,有時我們需要從過程中返回一個集合。即多條數據。這有幾種解決方案。比較簡單的做法是寫臨時表,但是這種做法不靈活。而且維護麻煩。我們可以使用嵌套表來實現.沒有一個集合類型能夠與java的jdbc類型匹配。這就是對象與關係數據庫的阻抗吧。數據庫的對象並不能夠完全轉換爲編程語言的對象,還必須使用關係數據庫的處理方式。

Java代碼 複製代碼
  1. create or replace package procpkg is   
  2.    type refcursor is ref cursor;   
  3.    procedure procrefcursor(p varchar2, p_ref_postypeList  out refcursor);   
  4. end procpkg;   
  5.   
  6. create or replace package body procpkg is   
  7.   procedure procrefcursor(p varchar2, p_ref_postypeList out  refcursor)   
  8.   is   
  9.     v_posTypeList PosTypeTable;   
  10.   begin   
  11.     v_posTypeList :=PosTypeTable();--初始化嵌套表   
  12.     v_posTypeList.extend;   
  13.     v_posTypeList(1) := PosType('A001','客戶資料變更');   
  14.     v_posTypeList.extend;   
  15.     v_posTypeList(2) := PosType('A002','團體資料變更');   
  16.     v_posTypeList.extend;   
  17.     v_posTypeList(3) := PosType('A003','受益人變更');   
  18.     v_posTypeList.extend;   
  19.     v_posTypeList(4) := PosType('A004','續期交費方式變更');   
  20.     open p_ref_postypeList for  select * from table(cast (v_posTypeList as PosTypeTable));   
  21.   end;   
  22. end procpkg;  
create or replace package procpkg is
   type refcursor is ref cursor;
   procedure procrefcursor(p varchar2, p_ref_postypeList  out refcursor);
end procpkg;

create or replace package body procpkg is
  procedure procrefcursor(p varchar2, p_ref_postypeList out  refcursor)
  is
    v_posTypeList PosTypeTable;
  begin
    v_posTypeList :=PosTypeTable();--初始化嵌套表
    v_posTypeList.extend;
    v_posTypeList(1) := PosType('A001','客戶資料變更');
    v_posTypeList.extend;
    v_posTypeList(2) := PosType('A002','團體資料變更');
    v_posTypeList.extend;
    v_posTypeList(3) := PosType('A003','受益人變更');
    v_posTypeList.extend;
    v_posTypeList(4) := PosType('A004','續期交費方式變更');
    open p_ref_postypeList for  select * from table(cast (v_posTypeList as PosTypeTable));
  end;
end procpkg;


在包頭中定義了一個遊標變量,並把它作爲存儲過程的參數類型。
在存儲過程中定義了一個嵌套表變量,對數據寫進嵌套表中,然後把嵌套表進行類型轉換爲table,遊標變量從這個嵌套表中進行查詢。外部程序調用這個遊標。
所以這個過程需要定義兩個類型。

Java代碼 複製代碼
  1. create or replace type PosType as Object (   
  2.   posType varchar2(20),   
  3.   description varchar2(50)   
  4. );  
create or replace type PosType as Object (
  posType varchar2(20),
  description varchar2(50)
);

create or replace type PosTypeTable is table of PosType;
需要注意,這兩個類型不能定義在包頭中,必須單獨定義,這樣java層才能使用。

在外部通過pl/sql來調用這個過程非常簡單。

Java代碼 複製代碼
  1. set serveroutput on;   
  2. declare    
  3.   type refcursor is ref cursor;   
  4.   v_ref_postype refcursor;   
  5.   v_postype varchar2(20);   
  6.   v_desc varchar2(50);   
  7. begin   
  8.   procpkg.procrefcursor('a',v_ref_postype);   
  9.   loop   
  10.     fetch  v_ref_postype into v_postype,v_desc;   
  11.     exit when v_ref_postype%notfound;   
  12.     dbms_output.put_line('posType:'|| v_postype || ';description:' || v_desc);   
  13.   end loop;   
  14. end;  
set serveroutput on;
declare 
  type refcursor is ref cursor;
  v_ref_postype refcursor;
  v_postype varchar2(20);
  v_desc varchar2(50);
begin
  procpkg.procrefcursor('a',v_ref_postype);
  loop
    fetch  v_ref_postype into v_postype,v_desc;
    exit when v_ref_postype%notfound;
    dbms_output.put_line('posType:'|| v_postype || ';description:' || v_desc);
  end loop;
end;


注意:對於遊標變量,不能使用for循環來處理。因爲for循環會隱式的執行open動作。而通過open for來打開的遊標%isopen是爲true的。也就是默認打開的。Open一個已經open的遊標是錯誤的。所以不能使用for循環來處理遊標變量。

我們主要討論的是如何通過jdbc調用來處理這個輸出參數。

Java代碼 複製代碼
  1. conn = this.getDataSource().getConnection();   
  2. CallableStatement call = conn.prepareCall("{call procpkg.procrefcursor(?,?)}");   
  3. call.setString(1null);   
  4. call.registerOutParameter(2, OracleTypes.CURSOR);   
  5. call.execute();   
  6. ResultSet rsResult = (ResultSet) call.getObject(2);   
  7. while (rsResult.next()) {   
  8.   String posType = rsResult.getString("posType");   
  9.   String description = rsResult.getString("description");   
  10.   ......   
  11. }  
conn = this.getDataSource().getConnection();
CallableStatement call = conn.prepareCall("{call procpkg.procrefcursor(?,?)}");
call.setString(1, null);
call.registerOutParameter(2, OracleTypes.CURSOR);
call.execute();
ResultSet rsResult = (ResultSet) call.getObject(2);
while (rsResult.next()) {
  String posType = rsResult.getString("posType");
  String description = rsResult.getString("description");
  ......
}


這就是jdbc的處理方法。

Ibatis處理方法:
1.參數配置

Java代碼 複製代碼
  1. <parameterMap id="PosTypeMAP" class="java.util.Map">    
  2.  <parameter property="p" jdbcType="VARCHAR" javaType="java.lang.String" />    
  3.  <parameter property="p_ref_postypeList" jdbcType="ORACLECURSOR" javaType="java.sql.ResultSet" mode="OUT" typeHandler="com.palic.elis.pos.dayprocset.integration.dao.impl.CursorHandlerCallBack" />    
  4. </parameterMap>   
  5.   
  6. 2.調用過程   
  7.   <procedure id ="procrefcursor" parameterMap ="PosTypeMAP">   
  8.       {call procpkg.procrefcursor(?,?)}   
  9.   </procedure>   
  10.   
  11. 3.定義自己的處理器   
  12.   public class CursorHandlerCallBack implements TypeHandler{   
  13.     public Object getResult(CallableStatement cs, int index) throws SQLException {   
  14.         ResultSet rs = (ResultSet)cs.getObject(index);   
  15.         List result = new ArrayList();   
  16.         while(rs.next()) {   
  17.             String postype =rs.getString(1);   
  18.             String description = rs.getString(2);   
  19.             CodeTableItemDTO posTypeItem = new CodeTableItemDTO();   
  20.             posTypeItem.setCode(postype);   
  21.             posTypeItem.setDescription(description);   
  22.             result.add(posTypeItem);   
  23.         }   
  24.         return result;   
  25.     }   
  26.   
  27.   
  28.   
  29. 4. dao方法   
  30.     public List procPostype() {   
  31.         String p = "";   
  32.         Map para = new HashMap();   
  33.         para.put("p",p);   
  34.         para.put("p_ref_postypeList",null);   
  35.          this.getSqlMapClientTemplate().queryForList("pos_dayprocset.procrefcursor",  para);   
  36.          return (List)para.get("p_ref_postypeList");   
  37.     }  
<parameterMap id="PosTypeMAP" class="java.util.Map"> 
 <parameter property="p" jdbcType="VARCHAR" javaType="java.lang.String" /> 
 <parameter property="p_ref_postypeList" jdbcType="ORACLECURSOR" javaType="java.sql.ResultSet" mode="OUT" typeHandler="com.palic.elis.pos.dayprocset.integration.dao.impl.CursorHandlerCallBack" /> 
</parameterMap>

2.調用過程
  <procedure id ="procrefcursor" parameterMap ="PosTypeMAP">
      {call procpkg.procrefcursor(?,?)}
  </procedure>

3.定義自己的處理器
  public class CursorHandlerCallBack implements TypeHandler{
	public Object getResult(CallableStatement cs, int index) throws SQLException {
		ResultSet rs = (ResultSet)cs.getObject(index);
        List result = new ArrayList();
		while(rs.next()) {
			String postype =rs.getString(1);
			String description = rs.getString(2);
			CodeTableItemDTO posTypeItem = new CodeTableItemDTO();
			posTypeItem.setCode(postype);
			posTypeItem.setDescription(description);
			result.add(posTypeItem);
		}
		return result;
	}



4. dao方法
	public List procPostype() {
		String p = "";
		Map para = new HashMap();
		para.put("p",p);
		para.put("p_ref_postypeList",null);
		 this.getSqlMapClientTemplate().queryForList("pos_dayprocset.procrefcursor",  para);
		 return (List)para.get("p_ref_postypeList");
	}


這個跟jdbc的方式非常的相似.
我們使用的是ibatis的2.0版本,比較麻煩。
如果是使用2.2以上版本就非常簡單的。
因爲可以在parameterMap中定義一個resultMap.這樣就無需要自己定義處理器了。
可以從分析2.0和2.0的dtd文件知道。

發佈了66 篇原創文章 · 獲贊 1 · 訪問量 2066
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章