SpringJDBC调用含TABLE OF NUMBER参数的存储过程报错求助
在Spring JDBC中调用Oracle存储过程MY_SP,该过程包含TABLE OF NUMBER类型的输入参数PARAM1,存储过程定义如下:
PROCEDURE MY_SP( PARAM1 IN TABLE OF NUMBER, PARAM2 IN NUMBER );
传入单元素整数数组(如[1])是合法的,但直接声明参数为OracleTypes.ARRAY并传入Integer[]时,出现报错:
Caused by: java.sql.SQLException: Fail to convert to internal representation: [Ljava.lang.Integer;@16a25973 at oracle.sql.ARRAY.toARRAY(ARRAY.java:308)
旧方案使用已废弃的ArrayDescriptor构建SQL Array,但属于旧版Spring JDBC用法。当前采用现代Spring JDBC的StoredProcedure接口实现(包含compile()),无法直接获取数据库连接,寻求可行解决思路。现有代码实现如下:
@Component public class MySP extends StoredProcedure { private RowMapper<List<Object>> rowMapper = new MyMapper(); public MySP(@Autowired DataSource dataSource) { super(dataSource, "MYPACKAGE.MY_SP"); declareParameter(new SqlParameter("PARAM1", OracleTypes.ARRAY)); //...其他参数声明 compile(); } public String callSP(Integer[] intArray) { //intArray为单元素整数数组,例如[1] Map<String, Object> inputParameters = new HashMap<>(); inputParameters.put("PARAM1", intArray); Map<String, Object> output = super.execute(inputParameters); // 处理结果... } }
1. 使用SqlValue接口包装数组(推荐)
Spring JDBC的SqlValue接口允许自定义参数处理逻辑,在参数设置时自动获取连接并创建Oracle兼容的ARRAY对象,无需手动管理连接:
修改callSP方法,将Integer[]包装为自定义SqlValue:
public String callSP(Integer[] intArray) { Map<String, Object> inputParameters = new HashMap<>(); inputParameters.put("PARAM1", new SqlValue() { @Override public void setValue(PreparedStatement ps, int paramIndex) throws SQLException { Connection conn = ps.getConnection(); // 替换为Oracle中实际定义的TABLE OF NUMBER类型别名(例:MYPACKAGE.NUMBER_TABLE) Array oracleArray = conn.createArrayOf("MYPACKAGE.NUMBER_TABLE", intArray); ps.setArray(paramIndex, oracleArray); } @Override public void cleanup() { // 可选:按需清理资源 } }); Map<String, Object> output = super.execute(inputParameters); // 处理结果... }
参数声明保持不变,核心是确保createArrayOf的第一个参数与数据库中定义的数组类型名称完全一致。
2. 自定义SqlParameter实现
如果需要复用数组参数的处理逻辑,可以自定义SqlParameter并重写setValue方法:
public class OracleArraySqlParameter extends SqlParameter { private final String arrayTypeName; public OracleArraySqlParameter(String name, String arrayTypeName) { super(name, OracleTypes.ARRAY); this.arrayTypeName = arrayTypeName; } @Override public void setValue(PreparedStatement ps, int paramIndex, Object value) throws SQLException { if (value instanceof Integer[]) { Connection conn = ps.getConnection(); Array oracleArray = conn.createArrayOf(arrayTypeName, (Integer[]) value); ps.setArray(paramIndex, oracleArray); } else { super.setValue(ps, paramIndex, value); } } }
然后在构造函数中替换原参数声明:
public MySP(@Autowired DataSource dataSource) { super(dataSource, "MYPACKAGE.MY_SP"); // 替换为实际的数组类型名称 declareParameter(new OracleArraySqlParameter("PARAM1", "MYPACKAGE.NUMBER_TABLE")); //...其他参数声明 compile(); }
调用时直接传入Integer[]即可,无需额外包装。
3. 切换为SimpleJdbcCall(现代Spring JDBC推荐方式)
如果允许调整实现方式,SimpleJdbcCall是Spring官方推荐的存储过程调用工具,支持更简洁的参数配置:
@Component public class MySPCaller { private final SimpleJdbcCall simpleJdbcCall; public MySPCaller(DataSource dataSource) { this.simpleJdbcCall = new SimpleJdbcCall(dataSource) .withProcedureName("MY_SP") .withSchemaName("MYPACKAGE") .declareParameters( new SqlParameter("PARAM1", OracleTypes.ARRAY, "MYPACKAGE.NUMBER_TABLE"), new SqlParameter("PARAM2", OracleTypes.NUMBER) ); } public String callSP(Integer[] intArray, Integer param2) { SqlParameterSource params = new MapSqlParameterSource() .addValue("PARAM1", intArray, OracleTypes.ARRAY, "MYPACKAGE.NUMBER_TABLE") .addValue("PARAM2", param2); Map<String, Object> output = simpleJdbcCall.execute(params); // 处理结果... } }
通过SqlParameter的第三个参数指定Oracle数组类型名称,Spring会自动完成数组到Oracle ARRAY的转换,无需手动操作连接。
内容的提问来源于stack exchange,提问作者gene b.

