Kotlin调用带INSERT操作的SQL存储过程时获取返回值结果集为null
你调用时拿到空ResultSet的核心原因,是SQL Server中DML操作生成的更新计数结果,挡在了后续SELECT结果的读取链路前面。
SQL Server JDBC驱动会严格按照存储过程内的语句执行顺序推送返回内容:
- INSERT、UPDATE、DELETE这类DML语句执行完成后,会优先返回对应操作的更新计数(也就是本次操作影响了多少行),这类结果不属于ResultSet
- 只有当SELECT语句执行完成后,才会生成可被读取的ResultSet
你现在的代码在execute()执行完之后直接读statement.resultSet,此时驱动推过来的第一个结果是INSERT产生的更新计数,根本没有关联对应的ResultSet,所以取出来就是null。如果把INSERT删掉,第一个推回来的结果就是SELECT生成的ResultSet,自然就能正常读到1234的值。
你可以用驱动自带的返回值判断逻辑确认问题点:
保留INSERT语句时,调用statement.execute()的返回值是false,代表当前拿到的是更新计数而非ResultSet,这时候读statement.updateCount能拿到值为1的影响行数——说明流程还停留在处理INSERT结果的阶段,根本没走到SELECT生成ResultSet的环节。
方案1:改存储过程(最省事,推荐)
在存储过程的BEGIN块开头加一行SET NOCOUNT ON。这行配置的作用就是告诉SQL Server,不要把DML语句的影响行数当成结果回传给调用端。
修改后的存储过程完整代码:
ALTER PROCEDURE [dbo].[TestProcedure] @varTest INT, @varTest2 INT AS BEGIN -- 新增配置,关闭更新计数回传 SET NOCOUNT ON DECLARE @returnValue AS INT SET @returnValue = 1234 INSERT INTO xTABLE(col1, col2) VALUES(1, 2) SELECT @returnValue AS returnValue RETURN END GO
加上这行配置后,你原来的Kotlin代码一行都不用改,就能正常拿到非空的ResultSet,顺利读到returnValue=1234的结果。
方案2:调整Kotlin端的结果遍历逻辑(没法改存储过程时用)
按照JDBC的规范,调用方需要把所有返回结果遍历完、消费掉前置的更新计数,才能拿到后续的ResultSet。具体实现就是循环判断当前的返回类型,跳过所有更新计数结果,直到拿到目标ResultSet:
val statement = connection.prepareCall("{CALL TestProcedure (1, 1)}") var currentIsResultSet = statement.execute() var targetRs: ResultSet? = null while (true) { if (currentIsResultSet) { // 当前返回的就是ResultSet,赋值后跳出循环 targetRs = statement.resultSet break } // 读当前的更新计数 val updateCount = statement.updateCount // updateCount等于-1代表没有更多返回结果了 if (updateCount == -1) { break } // 继续获取下一个返回结果 currentIsResultSet = statement.moreResults } // 拿到targetRs之后正常读returnValue即可
别把存储过程的两类返回值搞混:
- 你代码里
SELECT @returnValue AS returnValue生成的是结果集,必须通过ResultSet读取 - 存储过程末尾
RETURN返回的整数是存储过程的状态返回码,需要用{?= CALL TestProcedure(?, ?)}的格式注册输出参数才能读取,和ResultSet是两套独立的返回机制,不要混同。
内容的提问来源于stack exchange,提问作者Xose

