`

存储过程_转转转 _ibatis

    博客分类:
  • java
阅读更多

作者:袁光东
我们在进行pl/sql编程时打交道最多的就是存储过程了。存储过程的结构是非常的简单的,我们在这里除了学习存储过程的基本结构外,还会学习编写存储过程时相关的一些实用的知识。如:游标的处理,异常的处理,集合的选择等等

1.存储过程结构
1.1 第一个存储过程

Java代码
  1. create or replace procedure proc1(  
  2.   p_para1 varchar2,  
  3.   p_para2 out varchar2,  
  4.   p_para3 in out varchar2  
  5. )as   
  6.  v_name varchar2(20 );  
  7. begin  
  8.   v_name := '张三丰' ;  
  9.   p_para3 := v_name;  
  10.   dbms_output.put_line('p_para3:' ||p_para3);  
  11. end;  
create or replace procedure proc1(
  p_para1 varchar2,
  p_para2 out varchar2,
  p_para3 in out varchar2
)as 
 v_name varchar2(20);
begin
  v_name := '张三丰';
  p_para3 := v_name;
  dbms_output.put_line('p_para3:'||p_para3);
end;



上面就是一个最简单的存储过程。一个存储过程大体分为这么几个部分:
创建语句:create or replace procedure 存储过程名
如果没有or replace语句,则仅仅是新建一个存储过程。如果系统存在该存储过程,则会报错。Create or replace procedure 如果系统中没有此存储过程就新建一个,如果系统中有此存储过程则把原来删除掉,重新创建一个存储过程。
存储过程名定义:包括存储过程名和参数列表。参数名和参数类型。参数名不能重复, 参数传递方式:IN, OUT, IN OUT
IN 表示输入参数,按值传递方式。
OUT 表示输出参数,可以理解为按引用传递方式。可以作为存储过程的输出结果,供外部调用者使用。
IN OUT 即可作输入参数,也可作输出参数。
参数的数据类型只需要指明类型名即可,不需要指定宽度。
参数的宽度由外部调用者决定。
过程可以有参数,也可以没有参数
变量声明块:紧跟着的as (is )关键字,可以理解为pl/sql的declare关键字,用于声明变量。
变量声明块用于声明该存储过程需要用到的变量,它的作用域为该存储过程。另外这里声明的变量必须指定宽度。遵循PL/SQL的变量声明规范。
过程语句块:从begin 关键字开始为过程的语句块。存储过程的具体逻辑在这里来实现。
异常处理块:关键字为exception ,为处理语句产生的异常。该部分为可选
结束块:由end关键字结果。

1.2 存储过程的参数传递方式
存储过程的参数传递有三种方式:IN,OUT,IN OUT .
IN 按值传递,并且它不允许在存储过程中被重新赋值。如果存储过程的参数没有指定存参数传递类型,默认为IN

Java代码
  1. create or replace procedure proc1(  
  2.   p_para1 varchar2,  
  3.   p_para2 out varchar2,  
  4.   p_para3 in out varchar2  
  5. )as   
  6.  v_name varchar2(20 );  
  7. begin  
  8.   p_para1 :='aaa' ;  
  9.   p_para2 :='bbb' ;  
  10.   v_name := '张三丰' ;  
  11.   p_para3 := v_name;  
  12.   dbms_output.put_line('p_para3:' ||p_para3);  
  13.   null ;  
  14. end;  
  15.       
  16. Warning: Procedure created with compilation errors  
  17.   
  18. SQL> show error;  
  19. Errors for  PROCEDURE LIFEMAN.PROC1:  
  20.   
  21. LINE/COL ERROR  
  22. -------- ----------------------------------------------------------------------  
  23. 8 / 3       PLS- 00363 : expression  'P_PARA1'  cannot be used as an assignment target  
  24. 8 / 3       PL/SQL: Statement ignored  
create or replace procedure proc1(
  p_para1 varchar2,
  p_para2 out varchar2,
  p_para3 in out varchar2
)as 
 v_name varchar2(20);
begin
  p_para1 :='aaa';
  p_para2 :='bbb';
  v_name := '张三丰';
  p_para3 := v_name;
  dbms_output.put_line('p_para3:'||p_para3);
  null;
end;
	
Warning: Procedure created with compilation errors

SQL> show error;
Errors for PROCEDURE LIFEMAN.PROC1:

LINE/COL ERROR
-------- ----------------------------------------------------------------------
8/3      PLS-00363: expression 'P_PARA1' cannot be used as an assignment target
8/3      PL/SQL: Statement ignored

这一点与其它高级语言都不同。它相当于java在参数前面加上final关键字。


OUT 参数:作为输出参数,需要注意,当一个参数被指定为OUT类型时,就算在调用存储过程之前对该参数进行了赋值,在存储过程中该参数的值仍然是null.

Java代码
  1. create or replace procedure proc1(  
  2.   p_para1 varchar2,  
  3.   p_para2 out varchar2,  
  4.   p_para3 in out varchar2  
  5. )as   
  6.  v_name varchar2(20 );  
  7. begin  
  8.   v_name := '张三丰' ;  
  9.   p_para3 := v_name;  
  10.   dbms_output.put_line('p_para1:' ||p_para1);  
  11.   dbms_output.put_line('p_para2:' ||p_para2);  
  12.   dbms_output.put_line('p_para3:' ||p_para3);  
  13. end;  
  14.   
  15. SQL> var p1 varchar2(10 );  
  16. SQL> var p2 varchar2(10 );  
  17. SQL> var p3 varchar2(10 );  
  18. SQL> exec :p1 :='aaaa' ;  
  19. SQL> exec :p2 :='bbbb' ;  
  20. SQL> exec :p3 :='cccc' ;  
  21. SQL> exec proc1(:p1,:p2,:p3);  
  22. p_para1:aaaa  
  23. p_para2:  
  24. p_para3:张三丰  
  25. SQL> exec dbms_output.put_line(:p2);  
  26.   
  27.   
  28. PL/SQL procedure successfully completed  
  29. p2  
  30. ---------  
create or replace procedure proc1(
  p_para1 varchar2,
  p_para2 out varchar2,
  p_para3 in out varchar2
)as 
 v_name varchar2(20);
begin
  v_name := '张三丰';
  p_para3 := v_name;
  dbms_output.put_line('p_para1:'||p_para1);
  dbms_output.put_line('p_para2:'||p_para2);
  dbms_output.put_line('p_para3:'||p_para3);
end;

SQL> var p1 varchar2(10);
SQL> var p2 varchar2(10);
SQL> var p3 varchar2(10);
SQL> exec :p1 :='aaaa';
SQL> exec :p2 :='bbbb';
SQL> exec :p3 :='cccc';
SQL> exec proc1(:p1,:p2,:p3);
p_para1:aaaa
p_para2:
p_para3:张三丰
SQL> exec dbms_output.put_line(:p2);


PL/SQL procedure successfully completed
p2
---------


INOUT 是真正的按引用传递参数。即可作为传入参数也可以作为传出参数。

Java代码
  1. 1.3  存储过程参数宽度  
  2. create or replace procedure proc1(  
  3.   p_para1 varchar2,  
  4.   p_para2 out varchar2,  
  5.   p_para3 in out varchar2  
  6. )as   
  7.  v_name varchar2(2 );  
  8. begin  
  9.   v_name := p_para1;  
  10. end;  
  11.   
  12. SQL> var p1 varchar2(10 );  
  13. SQL> var p2 varchar2(20 );  
  14. SQL> var p3 varchar2(30 );  
  15. SQL> exec :p1 :='aaaaaa' ;  
  16. SQL> exec proc1(:p1,:p2,:p3);  
  17.       
  18.       
  19. ORA-06502 : PL/SQL: numeric or value error: character string buffer too small  
  20. ORA-06512 : at  "LIFEMAN.PROC1" , line  8   
  21. ORA-06512 : at line  1   
1.3 存储过程参数宽度
create or replace procedure proc1(
  p_para1 varchar2,
  p_para2 out varchar2,
  p_para3 in out varchar2
)as 
 v_name varchar2(2);
begin
  v_name := p_para1;
end;

SQL> var p1 varchar2(10);
SQL> var p2 varchar2(20);
SQL> var p3 varchar2(30);
SQL> exec :p1 :='aaaaaa';
SQL> exec proc1(:p1,:p2,:p3);
	
	
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "LIFEMAN.PROC1", line 8
ORA-06512: at line 1


首先,我们要明白,我们无法在存储过程的定义中指定存储参数的宽度,也就导致了我们无法在存储过程中控制传入变量的宽度。这个宽度是完全由外部传入时决定的。
我们再来看看OUT类型的参数的宽度。

Java代码
  1. create or replace procedure proc1(  
  2.   p_para1 varchar2,  
  3.   p_para2 out varchar2,  
  4.   p_para3 in out varchar2  
  5. )as   
  6.  v_name varchar2(2 );  
  7. begin  
  8.   p_para2 :='aaaaaaaaaaaaaaaaaaaa' ;  
  9. end;  
  10. SQL> var p1 varchar2(1 );  
  11. SQL> var p2 varchar2(1 );  
  12. SQL> var p3 varchar2(1 );  
  13. SQL> exec :p2 :='a' ;  
  14. SQL> exec proc1(:p1,:p2,:p3);  
create or replace procedure proc1(
  p_para1 varchar2,
  p_para2 out varchar2,
  p_para3 in out varchar2
)as 
 v_name varchar2(2);
begin
  p_para2 :='aaaaaaaaaaaaaaaaaaaa';
end;
SQL> var p1 varchar2(1);
SQL> var p2 varchar2(1);
SQL> var p3 varchar2(1);
SQL> exec :p2 :='a';
SQL> exec proc1(:p1,:p2,:p3);

在该过程中,p_para2被赋予了20个字符a.
而在外部的调用过程中,p2这个参数仅仅被定义为varchar2(1).
而把p2作为参数调用这个过程,却并没有报错。而且它的真实值就是20个a

Java代码
  1. SQL> select dump(:p2) from dual;  
  2. DUMP(:P2)  
  3. ---------------------------------------------------------------------------  
  4. Typ=1  Len= 20 97 , 97 , 97 , 97 , 97 , 97 , 97 , 97 , 97 , 97 , 97 , 97 , 97 , 97 , 97 , 97 , 97 , 97 , 97 , 97   
  5. p2  
  6. ---------  
  7. aaaaaaaaaaaaaaaaaaaa  
  8.       
  9.     再来看看IN OUT参数的宽度  
  10. create or replace procedure proc1(  
  11.   p_para1 varchar2,  
  12.   p_para2 out varchar2,  
  13.   p_para3 in out varchar2  
  14. )as   
  15.  v_name varchar2(2 );  
  16. begin  
  17.   p_para3 :='aaaaaaaaaaaaaaaaaaaa' ;  
  18. end;  
  19.   
  20. SQL> var p1 varchar2(1 );  
  21. SQL> var p2 varchar2(1 );  
  22. SQL> var p3 varchar2(1 );  
  23. SQL> exec proc1(:p1,:p2,:p3);  
SQL> select dump(:p2) from dual;
DUMP(:P2)
---------------------------------------------------------------------------
Typ=1 Len=20: 97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97
p2
---------
aaaaaaaaaaaaaaaaaaaa
	
	再来看看IN OUT参数的宽度
create or replace procedure proc1(
  p_para1 varchar2,
  p_para2 out varchar2,
  p_para3 in out varchar2
)as 
 v_name varchar2(2);
begin
  p_para3 :='aaaaaaaaaaaaaaaaaaaa';
end;

SQL> var p1 varchar2(1);
SQL> var p2 varchar2(1);
SQL> var p3 varchar2(1);
SQL> exec proc1(:p1,:p2,:p3);

执行这个过程,仍然正确执行。

可见,对于IN参数,其宽度是由外部决定。
对于OUT 和IN OUT 参数,其宽度是由存储过程内部决定。
因此,在写存储过程时,对参数的宽度进行说明是非常有必要的,最明智的方法就是参数的数据类型使用%type。这样双方就达成了一致。

1.3 参数的默认值
存储过程的参数可以设置默认值

Java代码
  1. create or replace procedure procdefault(p1 varchar2,  
  2.                                         p2 varchar2 default   'mark' )  
  3. as   
  4. begin  
  5.   dbms_output.put_line(p2);  
  6. end;  
  7.   
  8. SQL> set serveroutput on;  
  9. SQL> exec procdefault('a' );  
create or replace procedure procdefault(p1 varchar2,
                                        p2 varchar2 default 'mark')
as 
begin
  dbms_output.put_line(p2);
end;

SQL> set serveroutput on;
SQL> exec procdefault('a');

mark
可以通过default 关键字为存储过程的参数指定默认值。在对存储过程调用时,就可以省略默认值。
需要注意的是:默认值仅仅支持IN传输类型的参数。OUT 和 IN OUT不能指定默认值

对于有默认值的参数不是排在最后的情况。

Java代码
  1. create or replace procedure procdefault2(p1 varchar2  default   'remark' ,  
  2.                                         p2 varchar2 )  
  3. as   
  4. begin  
  5.   dbms_output.put_line(p1);  
  6. end;  
create or replace procedure procdefault2(p1 varchar2 default 'remark',
                                        p2 varchar2 )
as 
begin
  dbms_output.put_line(p1);
end;

第一个参数有默认值,第二个参数没有。如果我们想使用第一个参数的默认值时
exec procdefault2('aa');
这样是会报错的。
那怎么变呢?可以指定参数的值。

Java代码
  1. SQL> exec procdefault2(p2 => 'aa' );  
SQL> exec procdefault2(p2 =>'aa');


remark
这样就OK了,指定aa传给参数p2


2. 存储过程内部块
2.1 内部块
我们知道了存储过程的结构,语句块由begin开始,以end结束。这些块是可以嵌套。在语句块中可以嵌套任何以下的块。

Java代码
  1. Declare … begin … exception … end;  
  2. create or replace procedure innerBlock(p1 varchar2)  
  3. as   
  4.   o1 varchar2(10 ) :=  'out1' ;  
  5. begin  
  6.   dbms_output.put_line(o1);  
  7.   declare   
  8.     inner1 varchar2(20 );  
  9.   begin  
  10.     inner1 :='inner1' ;  
  11.     dbms_output.put_line(inner1);  
  12.   
  13.     declare   
  14.       inner2 varchar2(20 );  
  15.     begin  
  16.       inner2 := 'inner2' ;  
  17.       dbms_output.put_line(inner2);  
  18.     end;  
  19.   exception   
  20.     when others then  
  21.       null ;  
  22.   end;  
  23. end;  
Declare … begin … exception … end;
create or replace procedure innerBlock(p1 varchar2)
as 
  o1 varchar2(10) := 'out1';
begin
  dbms_output.put_line(o1);
  declare 
    inner1 varchar2(20);
  begin
    inner1 :='inner1';
    dbms_output.put_line(inner1);

    declare 
      inner2 varchar2(20);
    begin
      inner2 := 'inner2';
      dbms_output.put_line(inner2);
    end;
  exception 
    when others then
      null;
  end;
end;

需要注意变量的作用域。

3.存储过程的常用技巧
3.1 哪种集合?
我们在使用存储过程的时候经常需要处理记录集,也就是多条数据记录。分为单列多行和多列多行,这些类型都可以称为集合类型。我们在这里进行比较这些集合类型,以便于在编程时做出正确的选择。
索引表,也称为pl/sql表,不能存储于数据库中,元素的个数没有限制,下标可以为负值。

Java代码
  1. type t_table is table of varchar2( 20 ) index by binary_integer;  
  2.  v_student t_table;  
type t_table is table of varchar2(20) index by binary_integer;
 v_student t_table;

varchar2(20)表示存放元素的数据类型,binary_integer表示元素下标的数据类型。
嵌套表,索引表没有 index by子句就是嵌套表,它可以存放于数据中,元素个数无限,下标从1开始,并且需要初始化

Java代码
  1. type t_nestTable is table of varchar2( 20 );  
  2. v_class t_nestTable ;  
type t_nestTable is table of varchar2(20);
v_class t_nestTable ;

仅是这样声明是不能使用的,必须对嵌套表进行初始化,对嵌套表进行初始化可以使用它的构造函数

Java代码
  1. v_class :=t_nestTable( 'a' , 'b' , 'c' );  
v_class :=t_nestTable('a','b','c');

变长数组,变长数组与高级语言的数组类型非常相似,下标以1开始,元素个数有限。

Java代码
  1. type t_array is varray ( 20 ) of varchar2( 20 );  
type t_array is varray (20) of varchar2(20);


varray(20)就定义了变长数组的最大元素个数是20个
变长数组与嵌套表一样,也可以是数据表列的数据类型。
同时,变长数组的使用也需要事先初始化。

类型 可存储于数据库 元素个数 是否需初始化 初始下标值
索引表 否 无限 不需
嵌套表 可 无限 需 1
可变数组 可 有限(自定义) 需 1

由此可见,如果仅仅是在存储过程中当作集合变量使用,索引表是最好的选择。

3.2 选用何种游标?
显示游标分为:普通游标,参数化游标和游标变量三种。
下面以一个过程来进行说明

Java代码
  1. create or replace procedure proccursor(p varchar2)  
  2. as   
  3. v_rownum number(10 ) :=  1 ;  
  4. cursor c_postype is select pos_type from pos_type_tbl where rownum =1 ;  
  5. cursor c_postype1 is select pos_type from pos_type_tbl where rownum = v_rownum;  
  6. cursor c_postype2(p_rownum number) is select pos_type from pos_type_tbl where rownum = p_rownum;  
  7. type t_postype is ref cursor ;  
  8. c_postype3 t_postype;  
  9. v_postype varchar2(20 );  
  10. begin  
  11.   open c_postype;  
  12.   fetch c_postype into v_postype;  
  13.   dbms_output.put_line(v_postype);  
  14.   close c_postype;  
  15.   open c_postype1;  
  16.   fetch c_postype1 into v_postype;  
  17.   dbms_output.put_line(v_postype);  
  18.   close c_postype1;  
  19.   open c_postype2(1 );  
  20.   fetch c_postype2 into v_postype;  
  21.   dbms_output.put_line(v_postype);  
  22.   close c_postype2;  
  23.   open c_postype3 for  select pos_type from pos_type_tbl where rownum = 1 ;  
  24.   fetch c_postype3 into v_postype;  
  25.   dbms_output.put_line(v_postype);  
  26.   close c_postype3;  
  27. end;  
create or replace procedure proccursor(p varchar2)
as 
v_rownum number(10) := 1;
cursor c_postype is select pos_type from pos_type_tbl where rownum =1;
cursor c_postype1 is select pos_type from pos_type_tbl where rownum = v_rownum;
cursor c_postype2(p_rownum number) is select pos_type from pos_type_tbl where rownum = p_rownum;
type t_postype is ref cursor ;
c_postype3 t_postype;
v_postype varchar2(20);
begin
  open c_postype;
  fetch c_postype into v_postype;
  dbms_output.put_line(v_postype);
  close c_postype;
  open c_postype1;
  fetch c_postype1 into v_postype;
  dbms_output.put_line(v_postype);
  close c_postype1;
  open c_postype2(1);
  fetch c_postype2 into v_postype;
  dbms_output.put_line(v_postype);
  close c_postype2;
  open c_postype3 for select pos_type from pos_type_tbl where rownum =1;
  fetch c_postype3 into v_postype;
  dbms_output.put_line(v_postype);
  close c_postype3;
end;


cursor c_postype is select pos_type from pos_type_tbl where rownum =1
这一句是定义了一个最普通的游标,把整个查询已经写死,调用时不可以作任何改变。
cursor c_postype1 is select pos_type from pos_type_tbl where rownum = v_rownum;
这一句并没有写死,查询参数由变量v_rownum来决定。需要注意的是v_rownum必须在这个游标定义之前声明。
cursor c_postype2(p_rownum number) is select pos_type from pos_type_tbl where rownum = p_rownum;
这一条语句与第二条作用相似,都是可以为游标实现动态的查询。但是它进一步的缩小了参数的作用域范围。但是可读性降低了不少。
type t_postype is ref cursor ;
c_postype3 t_postype;
先定义了一个引用游标类型,然后再声明了一个游标变量。
open c_postype3 for select pos_type from pos_type_tbl where rownum =1;
然后再用open for 来打开一个查询。需要注意的是它可以多次使用,用来打开不同的查询。
从动态性来说,游标变量是最好用的,但是阅读性也是最差的。
注意,游标的定义只能用使关键字IS,它与AS不通用。

3.3 游标循环最佳策略
我们在进行PL/SQL编程时,经常需要循环读取结果集的数据。进行逐行处理,这个过程就需要对游标进行循环。对游标进行循环的方法有多种,我们在此一一分析。

Java代码
  1. create or replace procedure proccycle(p varchar2)  
  2. as   
  3. cursor c_postype is select pos_type, description from pos_type_tbl where rownum < 6 ;  
  4. v_postype varchar2(20 );  
  5. v_description varchar2(50 );  
  6. begin  
  7. open c_postype;  
  8.   if  c_postype%found then  
  9.     dbms_output.put_line('found true' );  
  10.   elsif c_postype%found = false  then  
  11.     dbms_output.put_line('found false' );  
  12.   else   
  13.     dbms_output.put_line('found null' );  
  14.   end if ;  
  15.   loop  
  16.    fetch c_postype into v_postype,v_description ;  
  17.    exit when c_postype%notfound;  
  18.    dbms_output.put_line('postype:' ||v_postype|| ',description:' ||v_description);  
  19.   end loop;  
  20.   close c_postype;  
  21. dbms_output.put_line('---loop end---' );  
  22.   open c_postype;  
  23.     fetch c_postype into v_postype,v_description;  
  24.     while  c_postype%found loop  
  25.       dbms_output.put_line('postype:' ||v_postype|| ',description:' ||v_description);  
  26.       fetch c_postype into v_postype,v_description ;  
  27.     end loop;  
  28.   
  29.   close c_postype;  
  30. dbms_output.put_line('---while end---' );  
  31.   for  v_pos in c_postype loop  
  32.     v_postype := v_pos.pos_type;  
  33.     v_description := v_pos.description;  
  34.     dbms_output.put_line('postype:' ||v_postype|| ',description:' ||v_description);  
  35.   end loop;  
  36.   dbms_output.put_line('---for end---' );  
  37. end;  
create or replace procedure proccycle(p varchar2)
as 
cursor c_postype is select pos_type, description from pos_type_tbl where rownum < 6;
v_postype varchar2(20);
v_description varchar2(50);
begin
open c_postype;
  if c_postype%found then
    dbms_output.put_line('found true');
  elsif c_postype%found = false then
    dbms_output.put_line('found false');
  else
    dbms_output.put_line('found null');
  end if;
  loop
   fetch c_postype into v_postype,v_description ;
   exit when c_postype%notfound;
   dbms_output.put_line('postype:'||v_postype||',description:'||v_description);
  end loop;
  close c_postype;
dbms_output.put_line('---loop end---');
  open c_postype;
    fetch c_postype into v_postype,v_description;
    while c_postype%found loop
      dbms_output.put_line('postype:'||v_postype||',description:'||v_description);
      fetch c_postype into v_postype,v_description ;
    end loop;

  close c_postype;
dbms_output.put_line('---while end---');
  for v_pos in c_postype loop
    v_postype := v_pos.pos_type;
    v_description := v_pos.description;
    dbms_output.put_line('postype:'||v_postype||',description:'||v_description);
  end loop;
  dbms_output.put_line('---for end---');
end;


使用游标之前需要开打游标,open cursor,循环完后再关闭游标close cursor.
这是使用游标应该慎记于心的法则。
上面的过程演示了游标循环的三种方法。
在讨论循环方法之前,我们先看看%found和%notfound这些游标的属性。

Java代码
  1. open c_postype;  
  2.  if  c_postype%found then  
  3.    dbms_output.put_line('found true' );  
  4.  elsif c_postype%found = false  then  
  5.    dbms_output.put_line('found false' );  
  6.  else   
  7.    dbms_output.put_line('found null' );  
  8.  end if ;  
 open c_postype;
  if c_postype%found then
    dbms_output.put_line('found true');
  elsif c_postype%found = false then
    dbms_output.put_line('found false');
  else
    dbms_output.put_line('found null');
  end if;

在打开一个游标之后,马上检查它的%found或%notfound属性,它得到的结果即不是true也不是false.而是null.必须执行一条fetch语句后,这些属性才有值。

第一种使用loop 循环

Java代码
  1. loop  
  2.    fetch c_postype into v_postype,v_description ;  
  3.    exit when c_postype%notfound;  
  4.    ……  
  5. end loop  
loop
   fetch c_postype into v_postype,v_description ;
   exit when c_postype%notfound;
   ……
end loop

这里需要注意,exit when语句一定要紧跟在fetch之后。必避免多余的数据处理。
处理逻辑需要跟在exit when之后。这一点需要多加小心。
循环结束后要记得关闭游标。

第二种使用while循环。

Java代码
  1.    fetch c_postype into v_postype,v_description;  
  2. while  c_postype%found loop  
  3.    ……  
  4.       fetch c_postype into v_postype,v_description ;  
  5. end loop;  
   fetch c_postype into v_postype,v_description;
while c_postype%found loop
   ……
      fetch c_postype into v_postype,v_description ;
end loop;


我们知道了一个游标打开后,必须执行一次fetch语句,游标的属性才会起作用。所以使用while 循环时,就需要在循环之前进行一次fetch动作。
而且数据处理动作必须放在循环体内的fetch方法之前。循环体内的fetch方法要放在最后。否则就会多处理一次。这一点也要非常的小心。
总之,使用while来循环处理游标是最复杂的方法。

第三种 for循环

Java代码
  1. for  v_pos in c_postype loop  
  2.    v_postype := v_pos.pos_type;  
  3.    v_description := v_pos.description;  
  4.    …  
  5.  end loop;  
 for v_pos in c_postype loop
    v_postype := v_pos.pos_type;
    v_description := v_pos.description;
    …
  end loop;

可见for循环是比较简单实用的方法。
首先,它会自动open和close游标。解决了你忘记打开或关闭游标的烦恼。
其它,自动定义了一个记录类型及声明该类型的变量,并自动fetch数据到这个变量中。
我们需要注意v_pos 这个变量无需要在循环外进行声明,无需要为其指定数据类型。
它应该是一个记录类型,具体的结构是由游标决定的。
这个变量的作用域仅仅是在循环体内。
把v_pos看作一个记录变量就可以了,如果要获得某一个值就像调用记录一样就可以了。
如v_pos.pos_type
由此可见,for循环是用来循环游标的最好方法。高效,简洁,安全。
但遗憾的是,常常见到的却是第一种方法。所以从今之后得改变这个习惯了。

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(1 null );  
  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" javaTyp

  


  
分享到:
评论

相关推荐

    Ibatis调用Oracle存储过程返回自定义类型

    Ibatis调用Oracle存储过程,返回自定义的类型。

    iBatis SQL Maps开发指南.pdf

    存储过程 parameterClass parameterMap Inline Parameter简介 resultClass resultMap cacheModel xmlResultName Parameter Map 和 Inline Parameter 元素 Inline Parameter Map 基本类型输入参数 Map类型输入参数 ...

    iBATIS 帮助文档

    存储过程.........................................................................................................................20 parameterClass.........................................................

    40道MyBatis面试题带答案(很全)

    MyBatis是一款优秀的持久层框架,用于支持普通SQL查询、存储过程和高级映射。它消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。 MyBatis前身叫iBatis,本是Apache的一个开源项目,2010年由apache ...

    MyBatis学习资料

    MyBatis是一款一流的支持自定义SQL、存储过程和高级映射的持久化框架 . 官网:http://www.mybatis.org/ 3. 优缺点 (1) 优点 a) 与JDBC比较 减少代码量 Sql与程序代码分离 简单 增强了移植性 b) 与...

    程序员面试刷题的书哪个好-LearnMybatis:学习Mybatis

    SQL、存储过程以及高级映射的优秀的持久层框架。 MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。 MyBatis 可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO( Plain Old Java ...

    springmybatis

    MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis使用简单的XML或注解用于配置和原始映射,将接口和Java的POJOs(Plan ...

    2021年最新java面试题--视频讲解(内部培训84个知识点超详细).rar

    Java面试题31.jdbc调用存储过程 Java面试题32.简单说一下你对jdbc的理解 Java面试题33.写一个jdbc的访问oracle的列子 Java面试题34.jdbc中preparedStatement比Statement的好处 Java面试题35.数据库连接池的作用 Java...

    在C#项目中如何使用NHibernate详解

    现代化大型项目通常使用独立的数据库来存储数据,其中以采用关系型数据库居多。用于开发项目的高级语言(C#、Java等)是面向对象的,而关系型数据库是基于关系的,两者之间的沟通需要一种转换,也就是对象/关系...

    最新Java面试题视频网盘,Java面试题84集、java面试专属及面试必问课程

    │ Java面试题31.jdbc调用存储过程.mp4 │ Java面试题32.简单说一下你对jdbc的理解.mp4 │ Java面试题33.写一个jdbc的访问oracle的列子.mp4 │ Java面试题34.jdbc中preparedStatement比Statement的好处.mp4 │ Java...

    Spring in Action(第2版)中文版

    14.2.2渲染被存储在外部的文本 14.2.3显示错误 14.3使用tile设计页面布局 14.3.1tile视图 14.3.2tile控制器 14.4使用jsp的替代技术 14.4.1使用velocity模板 14.4.2使用freemarker 14.5产生非html输出 ...

    Spring in Action(第二版 中文高清版).part2

    14.2.2 渲染被存储在外部的文本 14.2.3 显示错误 14.3 使用Tile设计页面布局 14.3.1 Tile视图 14.3.2 Tile控制器 14.4 使用JSP的替代技术 14.4.1 使用Velocity模板 14.4.2 使用FreeMarker 14.5 产生非...

    Spring in Action(第二版 中文高清版).part1

    14.2.2 渲染被存储在外部的文本 14.2.3 显示错误 14.3 使用Tile设计页面布局 14.3.1 Tile视图 14.3.2 Tile控制器 14.4 使用JSP的替代技术 14.4.1 使用Velocity模板 14.4.2 使用FreeMarker 14.5 产生非...

    Java面试宝典2010版

    3、存储过程与触发器必须讲,经常被面试到? 4、数据库三范式是什么? 5、说出一些数据库优化方面的经验? 6、union和union all有什么不同? 7.分页语句 8.用一条SQL语句 查询出每门课都大于80分的学生姓名 9.所有...

    最新Java面试宝典pdf版

    3、存储过程与触发器必须讲,经常被面试到? 92 4、数据库三范式是什么? 94 5、说出一些数据库优化方面的经验? 95 6、union和union all有什么不同? 96 7.分页语句 97 8.用一条SQL语句 查询出每门课都大于80分的学生...

    Java面试笔试资料大全

    3、存储过程与触发器必须讲,经常被面试到? 92 4、数据库三范式是什么? 94 5、说出一些数据库优化方面的经验? 95 6、union和union all有什么不同? 96 7.分页语句 97 8.用一条SQL语句 查询出每门课都大于80分的学生...

    JAVA面试宝典2010

    3、存储过程与触发器必须讲,经常被面试到? 92 4、数据库三范式是什么? 94 5、说出一些数据库优化方面的经验? 95 6、union和union all有什么不同? 96 7.分页语句 97 8.用一条SQL语句 查询出每门课都大于80分的学生...

    Java面试宝典-经典

    3、存储过程与触发器必须讲,经常被面试到? 92 4、数据库三范式是什么? 94 5、说出一些数据库优化方面的经验? 95 6、union和union all有什么不同? 96 7.分页语句 97 8.用一条SQL语句 查询出每门课都大于80分的学生...

    java面试题大全(2012版)

    3、存储过程与触发器必须讲,经常被面试到? 92 4、数据库三范式是什么? 94 5、说出一些数据库优化方面的经验? 95 6、union和union all有什么不同? 96 7.分页语句 97 8.用一条SQL语句 查询出每门课都大于80分的学生...

Global site tag (gtag.js) - Google Analytics