如何在JAVA端使Oracle存储过程串行地执行

如何在JAVA端使Oracle存储过程串行地执行
     我们知道给资源上锁可以使我们串行化地访问资源,oracle plsql 开发人员提供了DBMS_SQL 包用来管理USERL LOCK 锁资源。这种锁可以使得多个session 串行的执行某个存储过程,还可以用来排他的访问某个外部设备或服务,甚至可以检测事务的提交或回滚(提交或回滚时锁的释放)。

有人说我在java端调用db的存储过程,可以使用synchronized lock来串行的调用存储过程。那就不需要db lock呢?因为当java端应用服务器down的时候,存储过程已经在执行了,但是可能oracle sessionRACdb)并没有立即释放掉。当我们重启应用服务器后,其实后台的以前的存储过程还在执行,如果再次调用存储过程,这就无法保证存储过程的串行执行了。所以说存储过程的同步锁是必须放在Oracle db端的。

DBMS_LOCK包具有下面几个API,主要说明以下几个,其他的可以参考oracle相应文档,我们这里只用X锁(排他锁也称写锁)。

 

PROCEDURE  DBMS_LOCK.ALLOCATE_UNIQUE

    (lockname 
IN   VARCHAR2

    ,lockhandle OUT 
VARCHAR2

    ,expiration_secs 
IN   INTEGER   DEFAULT   864000 );

 

参数

描述

lockname

锁的名称

lockhandle

与该名称相对应的锁的唯一标识

expiration_secs

这种名称到锁的映射的保存时间

当多个session用同样的名字lockname来获取唯一标识字符串时,不同的session用同样名字获取的lockhandle是相同的,lockname是最大128位的字符串,而且是大小写敏感的,锁的名字最好不要用"ORA$"打头,这种锁的名称是被oracle保留的名称。DBMS_LOCK.ALLOCATE_UNIQUE执行完后就会commit所以不能被trigger调用。所有获得的映射都为存放在SYS用户DBMS_LOCK_ALLOCATED视图中。


   
   
   
   
FUNCTION  DBMS_LOCK.REQUEST
    (id 
IN   INTEGER
    ,lockmode 
IN   INTEGER   DEFAULT  X_MODE
    ,timeout 
IN   INTEGER   DEFAULT  MAXWAIT
    ,release_on_commit 
IN  BOOLEAN  DEFAULT  FALSE)
RETURN   INTEGER ;
 

   
   
   
   
FUNCTION  DBMS_LOCK.REQUEST
    (lockhandle 
IN   VARCHAR2
    ,lockmode 
IN   INTEGER   DEFAULT  X_MODE
    ,timeout 
IN   INTEGER   DEFAULT  MAXWAIT
    ,release_on_commit 
IN  BOOLEAN  DEFAULT  FALSE)
RETURN   INTEGER ;
 

参数

描述

id

锁的唯一标识

lockhandle

DBMS_LOCK.ALLOCATE_UNIQUE返回的handle

lockmode

锁的模式

timeout

等待时间

release_on_commit

COMMIT or ROLLBACK事务时是否释放锁

返回值

描述

0

成功申请到锁

1

超时

2

死锁

3

参数错误

4

已经拥有特定id或handle的锁

5

不合法的lockhandle

用户定义的锁标识必须在 0 1073741823. 锁标识在范围2000000000 2147483647 oracle公司预先保留。推荐用lockhandle的方法获得锁,因为锁的名称是比较容易辨别的,也是比较容易描述的。第一种方法不被oracle推荐。

在共享服务器模式和分布式事务时我们最好把release_on_commit设置为true。


   
   
   
   
FUNCTION  DBMS_LOCK.RELEASE
    (id 
IN   INTEGER )
RETURN   INTEGER ;

   
   
   
   
FUNCTION  DBMS_LOCK.RELEASE
    (lockhandle 
IN   VARCHAR2 )
RETURN   INTEGER ;

参数

描述

id

锁的数字标识

lockhandle

ALLOCATE_UNIQUE返回的锁的handle

返回值

描述

0

成功

3

参数错误

4

并没有拥有特定的锁

5

不合法的lockhandle

RELEASE 函数用来释放先前申请的锁。当锁不用时最好立即释放,这是很好的习惯。锁本身就是宝贵的资源,并且可以尽早释放被锁住的资源,而且可以有效地避免死锁。

如何使用这些api,很容易只要在我们的存储过程之前或者之后调用申请锁,释放锁(或者在事务提交或rollback的时候自动释放锁)就可以了,但这样也带来了存储过程代码的侵入性,每个存储过程都必须调用申请锁,释放锁。我们可以写一个wrapper把锁的申请和释放包裹起来。类似于模板模式。


create   or   replace  package FRM_TEST_TESTING  is
     
PROCEDURE  loop4_specific_round;

end  FRM_TEST_TESTING;

create   or   replace  package body FRM_TEST_TESTING  is

  
--  Function and procedure implementations
   -- PROCEDURE loop4_specific_round(p_loop_count IN INTEGER) AS
   PROCEDURE  loop4_specific_round  AS
  
  PRAGMA AUTONOMOUS_TRANSACTION;
  
  
BEGIN    
    
FOR  r  IN   1  ..  60  LOOP
      SYS.dbms_lock.sleep(
20 );
      
      DBMS_OUTPUT.PUT_LINE(
' During testing SP executing.  ' ||  to_char(sysdate, ' MM/DD/YYYY HH24:MI:SS ' ));
      
insert   into  TEST_TEST
        (name, Creationtime)
      
values
        (
' 1111 ' , sysdate);  
      
    
commit ;
    
END  LOOP;
   
    
  
END  loop4_specific_round;
end  FRM_TEST_TESTING;

create   or   replace  package frm_test_task_pkg  is
 
  
function   frm_test_task_func(i_lock_name  in   varchar2 , i_procname  in   varchar2 , i_expiration_time  Integer   default   864000 , i_wait_time  Integer   default  DBMS_LOCK.maxwait)  return   number ;

end  frm_test_task_pkg;


create   or   replace  package body frm_test_task_pkg  is

  
procedure  app_task_wrapper_proc(i_procname  in   varchar2 as
  
  PRAGMA AUTONOMOUS_TRANSACTION;  
   cur BINARY_INTEGER :
=  DBMS_SQL.OPEN_CURSOR;
   fdbk BINARY_INTEGER; 
  
begin
     DBMS_SQL.PARSE (cur, 
      
' begin  '   ||  i_procname   ||   ' ;end; ' ,
     DBMS_SQL.NATIVE);
     fdbk :
=  DBMS_SQL. execute  (cur);
     DBMS_OUTPUT.put_line(
' Fetch rows :  '   ||  fdbk);
     DBMS_SQL.close_cursor(cur);
     
commit ;
  
end  app_task_wrapper_proc;
  
  
function   frm_test_task_func(i_lock_name  in   varchar2 , i_procname  in   varchar2 , i_expiration_time  Integer   default   864000 , i_wait_time  Integer   default  DBMS_LOCK.maxwait)  return   number   is
      v_result     
number ;
      v_lockhandle 
varchar2 ( 200 );
      v_sid   pls_integer;       
   
begin
    dbms_lock.allocate_unique(i_lock_name, v_lockhandle, i_expiration_time);

    v_result :
=  dbms_lock.request(v_lockhandle, dbms_lock.x_mode, i_wait_time, true);
    
    
select  sys_context( ' USERENV ' , ' SID ' into  v_sid  from  dual;
    
    dbms_output.put_line (to_char(systimestamp,
' HH24:MI:SS.FF3 ' ) || ' : SID  ' || v_sid || '  requests lock ' );

    
if  v_result  <>   0   then
    dbms_output.put_line(
           
case  
              
when  v_result = 1   then   ' Timeout '
              
when  v_result = 2   then   ' Deadlock '
              
when  v_result = 3   then   ' Parameter Error '
              
when  v_result = 4   then   ' Already owned '
              
when  v_result = 5   then   ' Illegal Lock Handle '
            
end );
     
else  
          app_task_wrapper_proc(i_procname);
  
end   if ;
     
commit ;
     
return  v_result;
  EXCEPTION
   
WHEN  OTHERS
   
THEN
      
/**/ /*
      || Anonymous block inside the exception handler lets me declare
      || local variables to hold the error code information.
      
*/

      
DECLARE
         error_code 
NUMBER  : =  SQLCODE;
         error_msg  
VARCHAR2  ( 300 ) : =  SQLERRM;
      
BEGIN
           DBMS_OUTPUT.put_line(error_code 
||   ' '   || error_msg);
           
-- RE RAISE ERROR TO CLIENT
           raise;
      
END --  End of anonymous block.

  
end  frm_test_task_func;

end  frm_test_task_pkg;

 

java端,应用程序只需要调用frm_test_task_func,把需要串行化的存储过程作为参数传入。Java端还需要提供db锁的逻辑名,这样同步在相同的逻辑名的锁上的存储过程会同步执行。在实际存储过程参数比较复杂的情况下,传参可能是个问题。

其实还有一种方法,对已有的存储过程代码没有侵入性,申请和释放db锁在java端完成,oracle保证了session断掉后,会释放session占有的锁资源。所以如果应用服务器down掉后,session在经过一段时间后会被释放,锁资源也会被释放。现在要考虑的是是否在事务结束的时候自动释放锁,考虑到现在申请锁是由java端完成的,所以释放锁也由java端显式的调用release释放。


create   or   replace  package body frm_test_task_pkg  is

 
procedure  frm_test_lock_acquire(i_lock_name  in   varchar2 , i_expiration_time  in   Integer   default   864000 , i_wait_time  in   Integer   default  DBMS_LOCK.maxwait, o_result out  number , o_lockhandle out  varchar2 as
      v_result     
number ;
      v_lockhandle 
varchar2 ( 200 );
   
begin
    
-- acquire a unique lock id
    sys.dbms_lock.allocate_unique(i_lock_name, v_lockhandle, i_expiration_time);
    
    
-- acquire a lock
    v_result : =  sys.dbms_lock.request(v_lockhandle, dbms_lock.x_mode, i_wait_time, false);
  
    
-- set return values
     o_result : =  v_result;
     o_lockhandle :
=  v_lockhandle;
 
end  frm_test_lock_acquire;
 
 
function  frm_test_lock_release(i_lockhandle  in   varchar2 return   number   as
       v_result 
number ;
 
begin
      
-- release lock according to lockhandle
      v_result : =  sys.dbms_lock.release(i_lockhandle);
 
      
return  v_result;
 
end  frm_test_lock_release;

end  frm_test_task_pkg;

 

 

这样java端需要先调用frm_test_lock_acquire申请锁,然后执行用户逻辑的存储过程,最后在显式的调用frm_test_lock_release释放锁。如果不想每次用到的时候都去申请,释放锁,在java端也可以使用模板模式,假设子类实现execute方法来完成需要串行化执行的存储过程,这时要注意我们可能会将connection传入到子类的execute方法中,但是子类却不能将connection关闭掉,因为我们还需要在execute方法执行完后会用它来释放锁。当然如果子类真的把物理的connection关闭掉也没有问题,但是现在我们大都使用connection pool,把connection返回给pool的时候,session的锁资源并没有清除。这样还需要将传入到子类execute方法的connection封装一下,或者叫装饰一下,我们有两种解决方法:

·         oonnection wrapperclose并不关闭连接或者返回给pool

·         connection wrapperclose方法关闭连接,但是需要在关闭连接之前释放锁,然后在抽象父类的方法中只需判断connection是否close掉,connection.isClosed()方法,如果没有close就调用wrapeerclose方法,既释放锁又关闭连接。

 

总结一下:lock放在java端会出现意外的情况,锁就必须放在db端,为了避免对已有存储过程代码的侵入性,可以使用wrapper存储过程,由于动态执行存储过程传递参数是个难题,所以还是把锁的申请,释放放在了java端,也避免了对已有存储过程代码的侵入性,同时又懒得每次都去申请锁,释放锁,可以在java端使用模板模式,但是使用模板模式时又怕子类不小心关了connection,而没释放锁,就将connection封装了一下,将这个被封装后的connection传到了子类的execute方法中。




你可能感兴趣的:(如何在JAVA端使Oracle存储过程串行地执行)