跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

如何使用JDBC调用存储过程_CallableStatement注册OUT参数并获取返回值

必须在execute()前调用registerOutParameter,参数索引从1开始,类型用java.sql.Types常量;漏注册、顺序错、类型不匹配或取值时机不当均导致SQLException。 CallableStatement.registerOutParameter 什么时候必须调用 不注册就直接
getxxx()
,运行时必抛
sqlexception: invalid column index
或
invalid operation for a result set of type forward_only
—— 这不是驱动 bug,是 jdbc 规范强制要求。存储过程的 out/inout 参数在执行前必须显式声明类型,jdbc 驱动靠这个才知道底层怎么分配缓冲区、怎么转换字节流。 常见错误场景:只对 IN 参数设值,漏掉 OUT;或把
registerOutParameter
放在
execute()
之后;或参数序号从 0 开始(实际从 1 开始)。
registerOutParameter
必须在
execute()
或
executeUpdate()
之前调用 参数索引从
1
开始,和 SQL 中
?
出现顺序严格对应 类型要用
java.sql.Types
常量,比如
Types.VARCHAR
、
Types.INTEGER
,别传字符串或数据库原生类型名 如果存储过程有多个 OUT 参数,每个都得单独注册,不能跳过中间某个 Oracle 和 MySQL 的 OUT 参数类型写法差异 Oracle 存储过程常用
NUMBER
、
VARCHAR2
、
DATE
,MySQL 多用
INT
、
TEXT
、
DATETIME
,但 JDBC 层统一走
java.sql.Types
。问题出在 Oracle 的
NUMBER
映射上:如果过程返回的是小数(比如
NUMBER(5,2)
),注册成
Types.INTEGER
会截断;必须用
Types.DECIMAL
并配合
scale
参数。 Oracle
NUMBER
→ 优先选
Types.DECIMAL
,带精度:
cs.registerOutParameter(2, Types.DECIMAL, 10)
Oracle
DATE
/ MySQL
DATETIME
→ 统一用
Types.TIMESTAMP
,别用
Types.DATE
(会丢时间部分) MySQL 的
TEXT
或大字段 → 注册为
Types.LONGVARCHAR
或
Types.CLOB
,否则可能报
Data truncation
Oracle 的
REF CURSOR
→ 类型必须是
Types.OTHER
,取值时用
getResultSet()
获取 OUT 参数值的时机和顺序 必须在
execute()
成功返回后、且在关闭
CallableStatement
前调用
getXXX()
。一旦
close()
,所有 OUT 值丢失。另外,不能按“SQL 里写的顺序”乱取——必须严格按
registerOutParameter
时的索引位置取,哪怕中间有 IN 参数。 执行完
cs.execute()
后立刻取值,别等 ResultSet 处理完(除非是 REF CURSOR) 取值类型要和注册类型兼容:注册
Types.DECIMAL
就用
getBigDecimal(2)
,别用
getInt(2)
如果存储过程没给某个 OUT 参数赋值(比如 PL/SQL 里忘了
:=
),取出来是
null
(Java null),不是 0 或空字符串 对
Types.BOOLEAN
,MySQL 8.0+ 支持,老版本得用
TINYINT
模拟,取值用
getByte()
判断 0/1 CallableStatement 关闭前必须检查 SQLException 很多人以为只要
execute()
没抛异常,OUT 参数就一定拿到了。错。某些数据库(尤其是 Oracle)会在获取 OUT 值时才真正校验类型匹配或触发隐式转换失败,这时抛出的异常和执行阶段无关,容易被忽略。 在
cs.close()
前,所有
getXXX()
调用都要包 try-catch,或确保上层能捕获 典型错误信息:
ORA-06502: PL/SQL: numeric or value error
,往往是因为注册类型太窄,而过程返回了超长字符串或大数值 别依赖
cs.wasNull()
判断是否为空——它只对刚调用的
getXXX()
有效,且仅当该字段数据库值为 NULL 时才返回 true 批量调用多个存储过程时,每个
CallableStatement
的生命周期要独立管理,别复用同一实例去注册不同参数结构的过程 事情说清了就结束。最常出问题的地方不在语法,而在类型注册和取值之间那几毫秒的时序与类型契约——驱动不会替你猜意图,它只认你写死的
registerOutParameter
。

相关文章