Spring JDBC调用含高级返回类型的Oracle函数问题
解决Spring SimpleJdbcCall调用Oracle管道化自定义集合函数问题
问题核心
你调用的Oracle函数getAccountDetails是管道化函数,返回的tbl_wrap是基于自定义对象obj_wrap的集合类型(通常是嵌套表或可变数组)。要通过Spring的SimpleJdbcCall正确调用,需要完成Java类型映射、Oracle类型注册两步关键操作,你的现有代码没有处理自定义类型的映射,所以无法正确解析返回结果。
前提确认
Oracle数据库中必然存在以下两个自定义类型(否则函数无法编译通过):
-- 自定义对象类型,对应Java实体类 CREATE TYPE obj_wrap AS OBJECT( TRN_REF_NO NUMBER, AUT_DT DATE, DBT_CCY_CD VARCHAR2(xx), DBT_GRS_AM NUMBER, AM_DCM_PLC VARCHAR2(xx), BNC_FRS_LIN_TX VARCHAR2(xx), PMT_SRC_CD VARCHAR2(xx), customer_no NUMBER, ACCOUNT_no NUMBER, HERITAGE VARCHAR2(xx) ); -- 基于obj_wrap的集合类型,函数返回的tbl_wrap CREATE TYPE tbl_wrap AS TABLE OF obj_wrap;
解决方案步骤
1. 创建Java实体类映射Oracle自定义对象
实现SQLData接口,让JDBC能将Oracle的obj_wrap对象转换为Java对象:
import java.sql.Date; import java.sql.SQLData; import java.sql.SQLException; import java.sql.SQLInput; import java.sql.SQLOutput; public class ObjWrap implements SQLData { // 必须与Oracle中对象类型名完全一致(注意大小写匹配数据库配置) private String sqlType = "OBJ_WRAP"; // 对应Oracle对象的字段 private Long trnRefNo; private Date autDt; private String dbtCcyCd; private Double dbtGrsAm; private String amDcmPlc; private String bncFrsLinTx; private String pmtSrcCd; private Long customerNo; private Long accountNo; private String heritage; // Getter、Setter方法自行补充 @Override public String getSQLTypeName() throws SQLException { return sqlType; } @Override public void readSQL(SQLInput stream, String typeName) throws SQLException { this.sqlType = typeName; // 按Oracle对象字段顺序读取 this.trnRefNo = stream.readLong(); this.autDt = stream.readDate(); this.dbtCcyCd = stream.readString(); this.dbtGrsAm = stream.readDouble(); this.amDcmPlc = stream.readString(); this.bncFrsLinTx = stream.readString(); this.pmtSrcCd = stream.readString(); this.customerNo = stream.readLong(); this.accountNo = stream.readLong(); this.heritage = stream.readString(); } @Override public void writeSQL(SQLOutput stream) throws SQLException { // 若仅读取数据,可留空;若需写入数据库则对应写入字段 stream.writeLong(trnRefNo); stream.writeDate(autDt); stream.writeString(dbtCcyCd); stream.writeDouble(dbtGrsAm); stream.writeString(amDcmPlc); stream.writeString(bncFrsLinTx); stream.writeString(pmtSrcCd); stream.writeLong(customerNo); stream.writeLong(accountNo); stream.writeString(heritage); } }
2. 注册自定义类型到Oracle JDBC驱动
在调用函数前,需要将Oracle的自定义类型与Java类绑定,确保JDBC能识别:
import oracle.jdbc.OracleConnection; import org.springframework.jdbc.support.nativejdbc.OracleNativeJdbcExtractor; // 获取原生Oracle连接 OracleNativeJdbcExtractor extractor = new OracleNativeJdbcExtractor(); OracleConnection oracleConn = (OracleConnection) extractor.getNativeConnection( spaJdbcTemplate.getDataSource().getConnection() ); // 注册对象类型和集合类型 oracleConn.registerSQLType("OBJ_WRAP", ObjWrap.class); oracleConn.registerSQLType("TBL_WRAP", java.sql.Array.class, "OBJ_WRAP");
3. 正确配置SimpleJdbcCall调用函数
管道化函数的返回结果可以通过SqlReturnArray解析为Java集合,或者当作游标处理:
方式一:直接映射为List
import org.springframework.jdbc.core.SqlReturnArray; import org.springframework.jdbc.core.SqlParameter; import org.springframework.jdbc.core.SqlOutParameter; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.jdbc.core.simple.SimpleJdbcCall; import java.sql.Types; import java.util.ArrayList; import java.util.List; import java.sql.Array; import oracle.sql.STRUCT; SimpleJdbcCall call = new SimpleJdbcCall(spaJdbcTemplate) .withFunctionName("getAccountDetails") .declareParameters( new SqlParameter("v_acc_no", Types.NUMERIC), new SqlOutParameter("RETURN", oracle.jdbc.OracleTypes.ARRAY, "TBL_WRAP", new SqlReturnArray() { @Override protected Object[] extractArray(Array array) throws SQLException { STRUCT[] structs = (STRUCT[]) array.getArray(); List<ObjWrap> resultList = new ArrayList<>(); for (STRUCT struct : structs) { ObjWrap obj = (ObjWrap) struct.toJavaObject(ObjWrap.class); resultList.add(obj); } return resultList.toArray(); } }) ); SqlParameterSource paramMap = new MapSqlParameterSource() .addValue("v_acc_no", 1234567, Types.NUMERIC); List<ObjWrap> results = (List<ObjWrap>) call.executeFunction(List.class, paramMap);
方式二:当作游标处理(更简洁)
管道化函数可以直接当作游标返回,用RowMapper映射每一行数据:
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.SqlParameter; import org.springframework.jdbc.core.simple.SimpleJdbcCall; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.SqlParameterSource; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.List; SimpleJdbcCall call = new SimpleJdbcCall(spaJdbcTemplate) .withFunctionName("getAccountDetails") .withReturnValue() .declareParameters(new SqlParameter("v_acc_no", Types.NUMERIC)) .returningResultSet("RETURN", new RowMapper<ObjWrap>() { @Override public ObjWrap mapRow(ResultSet rs, int rowNum) throws SQLException { ObjWrap obj = new ObjWrap(); obj.setTrnRefNo(rs.getLong("TRN_REF_NO")); obj.setAutDt(rs.getDate("AUT_DT")); obj.setDbtCcyCd(rs.getString("DBT_CCY_CD")); obj.setDbtGrsAm(rs.getDouble("DBT_GRS_AM")); obj.setAmDcmPlc(rs.getString("AM_DCM_PLC")); obj.setBncFrsLinTx(rs.getString("BNC_FRS_LIN_TX")); obj.setPmtSrcCd(rs.getString("PMT_SRC_CD")); obj.setCustomerNo(rs.getLong("customer_no")); obj.setAccountNo(rs.getLong("ACCOUNT_no")); obj.setHeritage(rs.getString("HERITAGE")); return obj; } }); SqlParameterSource paramMap = new MapSqlParameterSource() .addValue("v_acc_no", 1234567, Types.NUMERIC); List<ObjWrap> results = (List<ObjWrap>) call.executeFunction(List.class, paramMap);
常见问题排查
- 类型名大小写不匹配:Oracle默认不区分大小写,但如果创建类型时用了双引号,Java中必须严格匹配大小写。
- 驱动版本问题:确保使用的Oracle JDBC驱动版本与数据库版本兼容,建议使用最新的
ojdbc8或更高版本。 - 权限问题:执行代码的数据库用户需要有访问
obj_wrap、tbl_wrap类型的权限。
内容的提问来源于stack exchange,提问作者Jack Bourner
相关产品推荐
相关产品推荐

