在使用 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: The statement did not return a result set.
问题 2: 我在 SSMS 中运行一个存储过程并引发错误,但是当我使用 JDBC .execute
存储过程时没有抛出异常。
为什么会出现这些问题,我该如何避免?
最佳答案
当我们在 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]
关于java - 如何使用 JDBC 从存储过程中获取*一切*,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42169951/