CallableStatement + registerOutParameter + 多行结果

Posted

技术标签:

【中文标题】CallableStatement + registerOutParameter + 多行结果【英文标题】:CallableStatement + registerOutParameter + multiple row result 【发布时间】:2011-05-09 20:36:12 【问题描述】:

我有一个如下形式的 SQL 语句:

BEGIN\n 
UPDATE tab 
SET stuff
WHERE stuff
RETURNING intA, intB, stringC
INTO ?,?,?

我已经注册了合适的 Out 参数。

这里我有一些问题:我是调用 stmt.executeQuery() 还是 stmt.execute()?此外,我知道通过普通的 SELECT 查询,我可以遍历 resultSet 并填充我的对象——多行 Out 参数的等价物是什么?

编辑: 也许我可以注册一个 CURSOR 类型的输出参数并循环遍历这个结果。

编辑2: 我可能有多个需要循环的结果集吗? 谢谢!

【问题讨论】:

【参考方案1】:

我相信您可以实现您想要的,但是您需要处理 PL/SQL 数组而不是游标或结果集。下面是一个演示。

我有一个名为TEST 的表,其结构如下:

SQL> 描述测试; 名称空?类型 ----------------------------------------- -------- - ---------------- 一个数字(38) B号(38) C号(38)

并包含以下数据:

SQL> 从测试中选择 *; 甲乙丙 ---------- ---------- ---------- 1 2 3 4 5 6 7 8 9

我需要为使用的每种类型的列创建一个数组类型。在这里,我只有NUMBERs,但如果你也有一个或多个VARCHAR2 列,你也需要为它们创建一个类型。

SQL> 创建类型 t_integer_array 作为整数表; 2 / 创建的类型。

表和任何必要的类型都是我们需要在数据库中设置的。完成后,我们可以编写一个简短的 Java 类来执行 UPDATE ... RETURNING ...,向 Java 返回多个值:

import java.math.BigDecimal;
import java.util.Arrays;
import java.sql.*;
import oracle.sql.*;
import oracle.jdbc.*;

public class UpdateWithBulkReturning 
    public static void main(String[] args) throws Exception 
        Connection c = DriverManager.getConnection(
            "jdbc:oracle:thin:@localhost:1521:XE", "user", "password");

        c.setAutoCommit(false);

        /* You need BULK COLLECT in order to return multiple rows. */
        String sql = "BEGIN UPDATE test SET a = a + 10 WHERE b <> 5 " +
                     "RETURNING a, b, c BULK COLLECT INTO ?, ?, ?; END;";

        CallableStatement stmt = c.prepareCall(sql);

        /* Register the out parameters.  Note that the third parameter gives
         * the name of the corresponding array type. */
        for (int i = 1; i <= 3; ++i) 
            stmt.registerOutParameter(i, Types.ARRAY, "T_INTEGER_ARRAY");
        

        /* Use stmt.execute(), not stmt.executeQuery(). */
        stmt.execute();

        for (int i = 1; i <= 3; ++i) 
            /* stmt.getArray(i) returns a java.sql.Array for the output parameter in
             * position i.  The getArray() method returns the data within this
             * java.sql.Array object as a Java array.  In this case, Oracle converts
             * T_INTEGER_ARRAY into a Java BigDecimal array. */
            BigDecimal[] nums = (BigDecimal[]) (stmt.getArray(i).getArray());
            System.out.println(Arrays.toString(nums));
        

        stmt.close();
        c.rollback();
        c.close();
    

当我运行它时,我得到以下输出:

C:\Users\Luke\stuff>java UpdateWithBulkReturning [11, 17] [2, 8] [3, 9]

显示的输出分别是从 ABC 列返回的值。每列只有两个值,因为我们过滤掉了B 等于 5 的行。

您可能希望将值按行分组,而不是按列分组。换句话说,您可能希望输出包含[11, 2, 3][17, 8, 9]。如果这就是你想要的,恐怕你需要自己做那部分。

【讨论】:

谢谢,将对此进行测试并报告 - 在哪里可以找到sql限定类型的映射(日期等) hmm 所以类型将成为 sql 表脚本的一部分 我想这里是类型列表:download.oracle.com/docs/cd/B28359_01/appdev.111/b28370/… 到目前为止一切顺利 :) 我想不可能打印出数据行 =/(我有很多变量)【参考方案2】:

要以 Luke Woodward 的回答为基础并完善我之前的回答,您可以创建一个 Oracle 类型,使用它来临时存储数据,然后返回一个带有更新的 sys_refcursor。

创建新类型:

CREATE OR REPLACE TYPE rowid_tab AS TABLE OF varchar2(30);

创建数据库函数:

CREATE OR REPLACE
FUNCTION update_tab
RETURN sys_refcursor
IS
    ref_cur sys_refcursor;
    v_tab rowid_tab;
BEGIN
    UPDATE tab 
    SET intA = intA+2
      , intB = intB*2
      , stringC = stringC||' more stuff.'
    RETURNING ROWID
    BULK COLLECT INTO v_tab;

    OPEN ref_cur FOR 
        WITH DATA AS (SELECT * FROM TABLE(v_tab))
        SELECT intA, intB, stringC
        FROM tab
        where rowid in (select * from data);
    RETURN ref_cur;
END;

现在,在你的 java 中调用函数:

import java.math.BigDecimal;
import java.util.Arrays;
import java.sql.*;
import oracle.sql.*;
import oracle.jdbc.*;

public class StructTest     
    public static void main(String[] args) 
        throws Exception 
       
        System.out.println("Start...");

        ResultSet results = null;
        Connection c = DriverManager.getConnection( "jdbc:oracle:thin:@localhost:1521:xe", "scott", "tiger");
        c.setAutoCommit(false);        

        String sql = "begin ? := update_tab(); end;";
        System.out.println("sql = "+sql);
        CallableStatement stmt = c.prepareCall(sql);        
        /* Register the out parameter. */
        System.out.println("register out param");
        stmt.registerOutParameter(1, OracleTypes.CURSOR);
        // get the result set
        stmt.execute();
        results = (ResultSet) stmt.getObject(1);
        while (results.next())
            System.out.println("intA: "+results.getString(1)+", intB: "+results.getString(2)+", stringC: "+results.getString(3));
        
        c.rollback();        
        c.close();    
    

根据我的测试数据,我得到了以下结果:

intA: 3, intB: 4, stringC: a more stuff.
intA: 6, intB: 10, stringC: C more stuff.
intA: 3, intB: 4, stringC: a more stuff.

【讨论】:

我会在另一个结果集中得到返回值吗?我期待多行。 对不起,我还以为你要排一排。对于多行,您可以打开一个引用游标来选择由 where 子句标识的记录。但是,这可能会返回比您刚刚更新的更多的行。 这看起来确实比我的回答更好。 +1 谢谢,迈克尔——我实际上已经接受了卢克的解决方案——在这里也为你投票

以上是关于CallableStatement + registerOutParameter + 多行结果的主要内容,如果未能解决你的问题,请参考以下文章

说说StatementPreparedStatement和CallableStatement的异同(转)

JDBC之Statement,PreparedStatement,CallableStatement的区别

CallableStatement + registerOutParameter + 多行结果

使用CallableStatement接口调用存储过程

JDBC - 语句、PreparedStatement、CallableStatement 和缓存

如何返回 callableStatement 结果?