这个让我发疯。
我正在我的一个junit测试中使用HSQLDB作为内存数据库,这是我为调用存储过程而编写的一组通用类。
初始化db:
@BeforeClass
public static void initializeDB() throws SQLException, ClassNotFoundException {
dataSource = new JDBCDataSource();
dataSource.setDatabase("jdbc:hsqldb:mem:testdb;shutdown=true");
dataSource.setLoginTimeout(10);
dataSource.setUser("sa");
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
statement.execute("CREATE TABLE users (id INTEGER, name VARCHAR(25), PRIMARY KEY(id))");
statement.close();
statement = connection.createStatement();
statement.executeUpdate("INSERT INTO users VALUES(1, 'Ramiro')");
statement.executeUpdate("INSERT INTO users VALUES(2, 'Chanukya')");
statement.close();
String storedProcedure1 = "CREATE PROCEDURE sp_say_hi(IN greeting_p VARCHAR(10)) "
+ "READS SQL DATA DYNAMIC RESULT SETS 2 "
+ "BEGIN ATOMIC "
+ "DECLARE result CURSOR WITH RETURN FOR SELECT COALESCE(greeting_p, 'Hi')+' '+name as greeting FROM users FOR READ ONLY; "
+ "DECLARE result1 CURSOR WITH RETURN FOR SELECT * FROM users FOR READ ONLY; "
+ "OPEN result; "
+ "OPEN result1; "
+ "END";
statement = connection.createStatement();
statement.execute(storedProcedure1);
statement.close();
connection.commit();
}
然后在准备语句的类中的某个点我使用setObject方法来注册输入参数:
...
CallableStatement callableStatement = con.prepareCall("{call sp_say_hi(?)}");
callableStatement.setObject("greeting_p", "Hola");
...
setObject方法抛出带有cause:
的JavaSqlExceptionCaused by: org.hsqldb.HsqlException: Column not found: greeting_p
at org.hsqldb.error.Error.error(Unknown Source)
at org.hsqldb.error.Error.error(Unknown Source)
通过使用调试器,我能够发现 org.hsqldb.jdbc.JDBCCallableStatement 类中有一个包私有方法,它是HSQLDB的CallableStatement实现,方法名是findParameterIndex,它是在setObject方法中调用,该方法检查提供的参数是否存在于过程的参数映射中:
int findParameterIndex(String parameterName) throws SQLException {
if (isClosed || connection.isClosed) {
checkClosed();
}
int index = parameterNameMap.get(parameterName, -1);
if (index >= 0) {
return index + 1;
}
throw JDBCUtil.sqlException(ErrorCode.JDBC_COLUMN_NOT_FOUND,
parameterName);
}
通过使用调试器探索该地图,我能够看到hsqldb错误地设置了参数名称,至少在此地图中是这样的:
[@p1, null, null, null, null, null, null, null]
我能够通过更改setObject方法调用中的名称来验证:
callableStatement.setObject("@p1", "Hola");
之后它运作良好。
奇怪的是,如果我使用DatabaseMetaData.getProcedureColumns方法检索该过程的元数据,那么从jdbc元数据的角度来看,param的名称是正确的:
DatabaseMetaData dbMetadata = con.getMetaData();
ResultSet rs = dbMetadata.getProcedureColumns(con.getCatalog(),
con.getSchema(),
"SP_SAY_HI",
"%_P");
while(rs.next()) {
// get stored procedure metadata
String procedureCatalog = rs.getString(1);
String procedureSchema = rs.getString(2);
String procedureName = rs.getString(3);
String columnName = rs.getString(4);
short columnReturn = rs.getShort(5);
int columnDataType = rs.getInt(6);
String columnReturnTypeName = rs.getString(7);
int columnPrecision = rs.getInt(8);
int columnByteLength = rs.getInt(9);
short columnScale = rs.getShort(10);
short columnRadix = rs.getShort(11);
short columnNullable = rs.getShort(12);
String columnRemarks = rs.getString(13);
System.out.println("stored Procedure name="+procedureName);
System.out.println("procedureCatalog=" + procedureCatalog);
System.out.println("procedureSchema=" + procedureSchema);
System.out.println("procedureName=" + procedureName);
System.out.println("columnName=" + columnName);
System.out.println("columnReturn=" + columnReturn);
System.out.println("columnDataType=" + columnDataType);
System.out.println("columnReturnTypeName=" + columnReturnTypeName);
System.out.println("columnPrecision=" + columnPrecision);
System.out.println("columnByteLength=" + columnByteLength);
System.out.println("columnScale=" + columnScale);
System.out.println("columnRadix=" + columnRadix);
System.out.println("columnNullable=" + columnNullable);
System.out.println("columnRemarks=" + columnRemarks);
}
打印出来:
stored Procedure name=SP_SAY_HI
procedureCatalog=PUBLIC
procedureSchema=PUBLIC
procedureName=SP_SAY_HI
columnName=GREETING_P
columnReturn=1
columnDataType=12
columnReturnTypeName=CHARACTER VARYING
columnPrecision=10
columnByteLength=0
columnScale=0
columnRadix=0
columnNullable=1
columnRemarks=null
请注意,该名称是大写的,我已经检查过了。我在setObject方法中将参数名称的注册更改为大写,以查看是否有帮助,但没有区别。
更新 我在HSQLDB问题跟踪系统中创建了一个故障单,他们只修复了它并致力于当前的重新发布:https://sourceforge.net/p/hsqldb/bugs/1431/
答案 0 :(得分:2)
我认为使用从PreparedStatement
继承的setX()
方法将变量绑定到存储过程调用时始终使用参数索引是最安全的,例如:
callableStatement.setString(1, "Hola");
来自Connection.prepareCall()
的JavaDocs:
当prepareCall方法完成时,某些驱动程序可能会将call语句发送到数据库;其他人可能要等到执行CallableStatement对象。
如果在调用execute()
之前驱动程序没有与数据库通信,那么它也无法知道参数的名称,因为JDBC查询中的绑定变量总是匿名。当绑定发生时,HSQLDB驱动程序似乎确实不知道实际的参数名称,因为它在内部使用占位符名称(@p1
等)。
答案 1 :(得分:2)
我在HSQLDB问题跟踪系统中创建了一个故障单,他们只修复了它并提交到下一个版本,以获取更多信息:https://sourceforge.net/p/hsqldb/bugs/1431/