一个JDBC驱动的陷阱

时间:2021-02-13 21:30:54

近期公司的项目在升级Oracle 12C,除了要替换wm_concat之类的不能用的函数之类的预料中的问题外,我还遇到了一个挺奇怪的JDBC驱动问题:


就是使用JDBC调用存储过程时,发现获取的CallableStatement接口对象的setObject方法,用“命名参数”时有问题。


为了描述方便,我写成了一个小DEMO。


首先,有一个存储过程

CREATE OR REPLACE PROCEDURE PRINT_USER
(
USERID IN VARCHAR2,
USERNAME IN VARCHAR2,
PRINTINFO OUT VARCHAR2
)
AS
BEGIN
PRINTINFO := '您的 用户ID是:['||USERID||'] 用户名是:['||USERNAME||']';
EXCEPTION
WHEN OTHERS THEN
PRINTINFO := SQLCODE||'-'||SQLERRM;
END;


然后,编写一个简单的Java程序来调用一下它,并打印出它的出参。

package com.dullchap.db.driverproblem;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class TestDao {

static Connection conn;
static ResultSet rs;
static PreparedStatement stmt;

public static void main(String[] args) throws Exception{
try {
//加载驱动,获取链接
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl1", "scott", "scott");

//获取存储过程执行对象
CallableStatement lcs_state = conn.prepareCall("BEGIN PRINT_USER(:USERID,:USERNAME,:PRINTINFO); END;");

//设置存储过程出/入参数
lcs_state.setObject("USERNAME", "张三");
lcs_state.setObject("USERID", "123");
lcs_state.registerOutParameter("PRINTINFO", java.sql.Types.VARCHAR);

//执行存储过程
lcs_state.executeUpdate();

//打印存储过程结果
System.out.println(lcs_state.getObject("PRINTINFO"));

} catch (Exception e) {
e.printStackTrace();
} finally {
//释放连接
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
}
}
}

未升级Oracle 12C前,执行结果当然是没问题的,如下:

您的   用户ID是:[123]    用户名是:[张三]



但升级了Oracle 12C,并使用新的jdbc驱动包(ojdbc7.jar)时,问题来了,发现打印结果如下:

您的   用户ID是:[张三]    用户名是:[123]


经调查之后,发现ojdbc7.jar完全忽略了CallableStatement对应对象的setObject方法的第一个参数,


它完全按照先后顺序来给存储过程入参赋值了。


也就是说,我如果把代码的setObject那两行交换一下顺序,程序的输出也是正常的。


我在Oracle官网也查看了一下,有提到setObject(String,Object)是不推荐使用的方法了。还是老老实实用回setObject(int,Ojbect),乖乖写问号吧。

package com.dullchap.db.driverproblem;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class TestDao {

static Connection conn;
static ResultSet rs;
static PreparedStatement stmt;

public static void main(String[] args) throws Exception{
try {
//加载驱动,获取链接
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl1", "scott", "scott");

//获取存储过程执行对象
// CallableStatement lcs_state = conn.prepareCall("BEGIN PRINT_USER(:USERID,:USERNAME,:PRINTINFO); END;");
CallableStatement lcs_state = conn.prepareCall("BEGIN PRINT_USER(?,?,?); END;");

//设置存储过程出/入参数
// lcs_state.setObject("USERNAME", "张三");
// lcs_state.setObject("USERID", "123");
// lcs_state.registerOutParameter("PRINTINFO", java.sql.Types.VARCHAR);
lcs_state.setObject(2, "张三");
lcs_state.setObject(1, "123");
lcs_state.registerOutParameter(3, java.sql.Types.VARCHAR);

//执行存储过程
lcs_state.executeUpdate();

//打印存储过程结果
// System.out.println(lcs_state.getObject("PRINTINFO"));
System.out.println(lcs_state.getObject(3));

} catch (Exception e) {
e.printStackTrace();
} finally {
//释放连接
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
}
}
}