如何使用JDBC从存储过程中获取* everything *

使用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存储过程时,不会抛出任何exception。

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

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

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

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

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

在许多情况下,只需添加SET NOCOUNT ON;即可避免问题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]