You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);

常见问题排查

  1. 类型名大小写不匹配:Oracle默认不区分大小写,但如果创建类型时用了双引号,Java中必须严格匹配大小写。
  2. 驱动版本问题:确保使用的Oracle JDBC驱动版本与数据库版本兼容,建议使用最新的ojdbc8或更高版本。
  3. 权限问题:执行代码的数据库用户需要有访问obj_wrap、tbl_wrap类型的权限。

内容的提问来源于stack exchange,提问作者Jack Bourner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 04:37:07