【问题标题】:How to get *everything* back from a stored procedure using JDBC如何使用 JDBC 从存储过程中获取*一切*
【发布时间】:2017-06-29 10:12:38
【问题描述】:

在使用 JDBC 处理 SQL Server 存储过程时,我偶尔会遇到两种形式的奇怪行为:

问题 1: 我在 SQL Server Management Studio (SSMS) 中运行一个存储过程,它返回一个结果集。但是,当我尝试时

try (CallableStatement cs = conn.prepareCall("{call dbo.TroublesomeSP}")) {
    ResultSet rs = cs.executeQuery();

我得到了异常

com.microsoft.sqlserver.jdbc.SQLServerException:语句未返回结果集。

问题 2: 我在 SSMS 中运行一个存储过程,它引发了一个错误,但是当我使用 JDBC 到 .execute 存储过程时没有抛出异常。

为什么会出现这些问题,我该如何避免?

【问题讨论】:

标签: java sql-server stored-procedures jdbc


【解决方案1】:

当我们在 JDBC 中执行存储过程时,我们会返回一系列零个或多个“结果”。然后我们可以通过调用CallableStatement#getMoreResults() 依次处理这些“结果”。每个“结果”可以包含

  • 我们可以使用ResultSet 对象检索零或多行数据,
  • 我们可以使用CallableStatement#getUpdateCount() 检索的 DML 语句(INSERT、UPDATE、DELETE)的更新计数,或
  • 引发 SQLServerException 的错误。

对于“问题 1”,问题通常是存储过程不以 SET NOCOUNT ON; 开头,并在执行 SELECT 以生成结果集之前执行 DML 语句。 DML 的更新计数作为第一个“结果”返回,数据行“卡在它后面”,直到我们调用 getMoreResults

“问题2”本质上是相同的问题。在错误发生之前,存储过程会产生一个“结果”(通常是一个 SELECT,或者可能是一个更新计数)。该错误在随后的“结果”中返回,并且在我们使用getMoreResults“检索”它之前不会导致异常。

在许多情况下,只需将SET NOCOUNT ON; 添加为存储过程中的第一个可执行语句即可避免该问题。然而,对存储过程的更改并不总是可能的,事实仍然是,为了从存储过程中获取 所有内容,我们需要继续调用 getMoreResults 直到,正如 Javadoc 所说:

There are no more results when the following is true: 

     // stmt is a Statement object
     ((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1))

这听起来很简单,但就像往常一样,“魔鬼在细节中”,如下例所示。对于 SQL Server 存储过程...

ALTER PROCEDURE dbo.TroublesomeSP AS
BEGIN
    -- note: no `SET NOCOUNT ON;`
    DECLARE @tbl TABLE (id VARCHAR(3) PRIMARY KEY);

    DROP TABLE NonExistent;
    INSERT INTO @tbl (id) VALUES ('001');
    SELECT id FROM @tbl;
    INSERT INTO @tbl (id) VALUES ('001');  -- duplicate key error
    SELECT 1/0;  -- error _inside_ ResultSet
    INSERT INTO @tbl (id) VALUES ('101');
    INSERT INTO @tbl (id) VALUES ('201'),('202');
    SELECT id FROM @tbl;
END

...下面的 Java 代码将返回所有内容...

try (CallableStatement cs = conn.prepareCall("{call dbo.TroublesomeSP}")) {
    boolean resultSetAvailable = false;
    int numberOfResultsProcessed = 0;
    try {
        resultSetAvailable = cs.execute();
    } catch (SQLServerException sse) {
        System.out.printf("Exception thrown on execute: %s%n%n", sse.getMessage());
        numberOfResultsProcessed++;
    }
    int updateCount = -2;  // initialize to impossible(?) value
    while (true) {
        boolean exceptionOccurred = true; 
        do {
            try {
                if (numberOfResultsProcessed > 0) {
                    resultSetAvailable = cs.getMoreResults();
                }
                exceptionOccurred = false;
                updateCount = cs.getUpdateCount();
            } catch (SQLServerException sse) {
                System.out.printf("Current result is an exception: %s%n%n", sse.getMessage());
            }
            numberOfResultsProcessed++;
        } while (exceptionOccurred);

        if ((!resultSetAvailable) && (updateCount == -1)) {
            break;  // we're done
        }

        if (resultSetAvailable) {
            System.out.println("Current result is a ResultSet:");
            try (ResultSet rs = cs.getResultSet()) {
                try {
                    while (rs.next()) {
                        System.out.println(rs.getString(1));
                    }
                } catch (SQLServerException sse) {
                    System.out.printf("Exception while processing ResultSet: %s%n", sse.getMessage());
                }
            }
        } else {
            System.out.printf("Current result is an update count: %d %s affected%n",
                    updateCount,
                    updateCount == 1 ? "row was" : "rows were");
        }
        System.out.println();
    }
    System.out.println("[end of results]");
}

...产生以下控制台输出:

Exception thrown on execute: Cannot drop the table 'NonExistent', because it does not exist or you do not have permission.

Current result is an update count: 1 row was affected

Current result is a ResultSet:
001

Current result is an exception: Violation of PRIMARY KEY constraint 'PK__#314D4EA__3213E83F3335971A'. Cannot insert duplicate key in object 'dbo.@tbl'. The duplicate key value is (001).

Current result is a ResultSet:
Exception while processing ResultSet: Divide by zero error encountered.

Current result is an update count: 1 row was affected

Current result is an update count: 2 rows were affected

Current result is a ResultSet:
001
101
201
202

[end of results]

【讨论】:

  • 解释很好。 JDBC voodoo 很臭。
猜你喜欢
  • 1970-01-01
  • 2017-07-24
  • 2011-09-18
  • 2019-10-09
  • 2018-10-06
  • 1970-01-01
  • 2018-03-04
  • 2014-11-24
相关资源
最近更新 更多