在 Oracle 11g 中执行 PL/SQL 块并在 Java 客户端中处理游标

Posted

技术标签:

【中文标题】在 Oracle 11g 中执行 PL/SQL 块并在 Java 客户端中处理游标【英文标题】:Execute PL/SQL block in Oracle 11g and Process the cursor in Java client 【发布时间】:2018-12-12 17:44:39 【问题描述】:

我们正在尝试实施一个需要我们这样做的解决方案,

    频繁轮询控制表以查询未处理的事务

    一旦我们从步骤 1 中提取交易标识符,我们就必须查询详细信息。这一步是现有功能,只是我们通过在第一步中加入控制表来进行全扫描。

当有卷时,现有解决方案开始减慢处理速度。因此,我们决定从状态表中提取未处理的事务并进行 PK 查找以查询详细信息。

我知道这不是一个理想的解决方案,如适配器、CDC 或在视图中表达完整的细节。我们受到限制,因为产品合同禁止我们在源模式或源 Oracle 实例中的任何位置创建任何类型的对象,这让我们轮询表,并且此解决方案需要可扩展且接近实时(5 秒延迟) .

DDL:

 CREATE TABLE "CONTROL_TABLE"
(   
"REF_NO" VARCHAR2(16 CHAR), 
"BRANCH" VARCHAR2(3 CHAR),
"INIT_DATE" DATE, 
"STATUS" VARCHAR2(1 CHAR), 
 CONSTRAINT "CONST_CONTROL_TABLE" PRIMARY KEY ("REF_NO")
)

以下是我作为 POC 尝试过的,执行以下块, 1. 创建一个块来运行 SELECT for UPDATE,如下所示。在下面的块中,我选择使用“SKIP LOCKED”进行更新并更新游标范围内的记录。

 DECLARE
   CURSOR tCURSOR IS
     SELECT REF_NO FROM CONTROL_TABLE 
        WHERE STATUS = 'U' FOR UPDATE OF STATUS SKIP LOCKED;
 BEGIN
  FOR tCURSORREC IN tCURSOR LOOP
    UPDATE CONTROL_TABLE SET STATUS='W' WHERE STATUS='U';    
  END LOOP; 
  COMMIT;  
 END;  

上面的块工作得很好,我已经通过随机插入新记录和更新来自不同客户端会话的状态进行了测试。我的问题是我想让光标返回到 Java 客户端进行下游处理。参考之前的一篇文章,其中只有一个 SELECT 查询将游标绑定变量返回到 Java。非常感谢任何有用的指针。 Execute anonymous pl/sql block and get resultset in java

运行该块的Java sn-p,

    public static void main(String args[]) throws Exception
    
       final Connection c = DriverManager.getConnection("jdbc:oracle:thin:<<service_name>>:8888:<<schema>>", "user", "passwd");

     String plsql = "declare\r\n" + 
            "  cursor tCursor is\r\n" + 
            "    select ref_no from CONTROL_TABLE \r\n" + 
            "        where status = 'U' for update of REF_NO,STATUS skip locked;\r\n" + 
            "begin\r\n" +        
            "  for tCursorRec in tCursor loop\r\n" + 
            "    update CONTROL_TABLE set status='W' where status ='U';    \r\n" + 
            "  end loop;\r\n" +             
            "  commit;  \r\n" + 
            "? := tCursor" +
            "end;";     

            CallableStatement cs = c.prepareCall(plsql);
            cs.registerOutParameter(1, OracleTypes.CURSOR);
            cs.execute();          

            ResultSet cursorResultSet = (ResultSet) cs.getObject(1);
            while (cursorResultSet.next ())
            
               System.out.println (cursorResultSet.getString(1));
             
                cs.close();
                c.close();

   Exception:Exception in thread "main" java.sql.SQLException: ORA-06550: 
   line 10, column 8:
   PLS-00382: expression is of wrong type
   ORA-06550: line 10, column 1:

【问题讨论】:

PL/SQL 显式游标与 ref 游标不同,后者正是 Java 端所寻找的。但是你有一个更深层次的问题——除了每次在循环中更新表中的所有“U”行之外,无论你是否锁定它们——因为当你尝试分配游标时,游标已经用尽了;而且您无法重新打开它,因为您更改了数据,因此它找不到任何东西。您确定要在循环中更新并提交(释放锁),而不是进行进一步处理吗?无论哪种方式,您都希望将 ref 编号列表返回给 Java,对吗? Alex,在 Java 中获得 REF_NO 是我的目标。另一个线程也不应该提取相同的参考编号。这就是选择 FOR ... UPDATE 的原因。谢谢.. 【参考方案1】:

使用引用游标是有问题的,因为您已经用尽了现有的 PL/SQL 游标(无论如何它与引用游标的类型不同,因此会出现错误),并且您无法重新查询,因为您已经更新并提交了更改。无论如何,提交确实应该从 Java 端完成,但这是一个单独的问题。

您可以使用集合代替,例如使用a built-in varray type:

    Connection c = ods.getConnection();

    String plsql = "declare\r\n" +
        "  cursor tCursor is\r\n" +
        "    select ref_no from CONTROL_TABLE\r\n" +
        "        where status = 'U' for update of STATUS skip locked;\r\n" +
        "  array SYS.ODCIVARCHAR2LIST := new SYS.ODCIVARCHAR2LIST();\r\n" +
        "begin\r\n" +
        "  for tCursorRec in tCursor loop\r\n" +
        "    update CONTROL_TABLE set status='W' where current of tCURSOR;\r\n" +
        "    array.extend();\r\n" +
        "    array(array.count) := tCURSORREC.REF_NO;\r\n" +
        "  end loop;\r\n" +
        "  commit;\r\n" +
        "  ? := array;\r\n" +
        "end;";

    CallableStatement cs = c.prepareCall(plsql);
    cs.registerOutParameter(1, java.sql.Types.ARRAY, "SYS.ODCIVARCHAR2LIST");
    cs.execute();

    String[] refNos = (String[]) cs.getArray(1).getArray();

    cs.close();
    c.close();

    for (String refNo : refNos)
    
        // Whatever processing you want to do
        System.out.println(refNo);
    

生成的 PL/SQL 块最终为:

declare
  cursor tCursor is
    select ref_no from CONTROL_TABLE
        where status = 'U' for update of STATUS skip locked;
  array SYS.ODCIVARCHAR2LIST := new SYS.ODCIVARCHAR2LIST();
begin
  for tCursorRec in tCursor loop
    update CONTROL_TABLE set status='W' where current of tCURSOR;
    array.extend();
    array(array.count) := tCURSORREC.REF_NO;
  end loop;
  commit;
  ? := array;
end;

在游标循环中,array 集合被扩展(因此它的末尾有一个空元素),并且该游标行的 ref_no 被添加到该位置的数组中。

我还将更新更改为一次仅适用于一行,使用 where current of - 它以 for update 锁定的行为目标。在您的原始版本中,您正在使用“U”对所有行进行无限制更新;不仅仅是那些你已经锁定的,而且由于它们都在第一轮更新,循环的后续迭代没有任何东西需要更新。

在 Java 端,绑定变量也是一个数组,可以转换为适当类型的本地数组。

您也许可以通过以下方式避免显式光标:

begin
  update CONTROL_TABLE set status='W' where status = 'U'
  returning REF_NO
  bulk collect into ?;
  commit;
end;
/

【讨论】:

亚历克斯,非常感谢。测试工作,但老实说,我正在努力解决这个问题.. 将尝试将提交拉出块。 @MAYBEMEDIC - 我添加了更多解释;但也稍微简化了 Java 的分配和处理。 Alex,非常感谢您的扩展更新。我正在试验解决方案。

以上是关于在 Oracle 11g 中执行 PL/SQL 块并在 Java 客户端中处理游标的主要内容,如果未能解决你的问题,请参考以下文章

PL/SQL 编程:使用块标签来区分变量

在 Oracle 11g Express Edition 中运行 PL/SQL 代码 - 错误

并行执行 oracle PL/SQL [重复]

oracle 11g 中 for 循环中的 PL/SQL 限制

Oracle pl sql异常块将在何时执行

用于 oracle 11g 的 PL/SQL 中的嵌入式脚本 [重复]