繁体   English   中英

使用JDBC以自定义类型输入参数调用PL / SQL存储过程,所有字段均为空

[英]Using JDBC to call a PL/SQL stored procedure with custom type input parameter, all fields are null

我正在使用JDBC和createStruct()来调用Oracle数据库上的存储过程,该数据库接受自定义类型作为参数。 存储过程将自定义类型字段插入到表中,当我稍后从表中SELECT ,我看到我尝试插入的所有字段都是NULL

自定义类型如下所示:

type record_rec as object (owner_id varchar2 (7),
                                        target_id VARCHAR2 (8),
                                        IP VARCHAR2 (15),
                                        PREFIX varchar2 (7),
                                        port varchar2 (4),
                                        description VARCHAR2 (35),
                                        cost_id varchar2(10))

存储过程如下所示:

package body            "PKG_RECORDS"
IS
procedure P_ADD_RECORD (p_target_id in out VARCHAR2,
                      p_record_rec in record_rec)
is
  l_target_id   targets.target_id%TYPE;
BEGIN
  Insert into targets (target_id,
                      owner_id,
                      IP,
                      description,
                      prefix,
                      start_date,
                      end_date,
                      cost_id,
                      port,
                      server_name,
                      server_code)
       values (f_sequence ('TARGETS'),
               p_record_rec.owner_id,
               p_record_rec.ip,
               p_record_rec.description,
               p_record_rec.prefix,
               sysdate,
               to_date ('01-JAN-2050'),
               p_record_rec.cost_id,
               p_record_rec.port,
               'test-server',
               '51')
    returning target_id
         into p_target_id;
 END;
END PKG_RECORDS;

我的Java代码看起来像这样:

try (Connection con = m_dataSource.getConnection()) {
    ArrayList<String> ids = new ArrayList<>();
    CallableStatement call = con.prepareCall("{call PKG_RECORDS.P_ADD_RECORD(?,?)}");
    for (Record r : records) {
        call.registerOutParameter("p_target_id", Types.VARCHAR);
        call.setObject("p_record_rec", 
                con.createStruct("SCHEME_ADM.RECORD_REC", new Object[] {
                        r.getTarget_id(),
                        null, // will be populated by SP
                        t.getIp(),
                        t.getPrefix(),
                        t.getPort(),
                        t.getDescription(),
                        t.getCost_id()
                }), Types.STRUCT);
        call.execute();
        ids.add(call.getString("p_target_id"));
    }

    return new QueryRunner().query(con, 
            "SELECT * from TARGETS_V WHERE TARGET_ID IN ("+
                    ids.stream().map(s -> "?").collect(Collectors.joining(",")) +
                    ")",
            new BeanListHandler<Record>(Record.class),
            ids.toArray(new Object[] {})
            ).stream()
            .collect(Collectors.toList());
} catch (SQLException e) {
    throw new DataAccessException(e.getMessage());
}

注意:*最后一部分是使用Apache Commons db-utils - 我喜欢他们的bean流操作。 *连接使用C3P0连接池 - 可能是相关的吗? *只是为了说清楚 - 它不是bean处理器将空值填充到Record bean字段中 - 如果我使用SQL资源管理器直接加载表(或视图),我可以看到数据库中的字段确实已设置为NULL

进程运行时没有SQLException ,或者有任何其他错误通知。

任何想法要检查什么?

[更新]在阅读了Oracle Objects和SQLData映射之后,我重新编写了使用SQLData的代码。

Record类现在实现SQLData ,它的writeSQL()方法如下所示:

@Override
public void writeSQL(SQLOutput stream) throws SQLException {
    stream.writeString(owner_id);
    stream.writeString(target_id);
    stream.writeString(Objects.isNull(ip) ? "0" : ip); // weird, but as specified
    stream.writeString(prefix);
    stream.writeString(String.valueOf(port));
    stream.writeString(description);
    stream.writeString(cost_id);
}

然后在调用代码的开头,我添加了:

con.getTypeMap().put("SCHEME_ADM.RECORD_REC", Record.class);

而不是使用createStruct()setObject()调用现在看起来像这样:

call.setObject("p_record_rec", t, Types.STRUCT)

但结果是相同的 - 没有错误,所有传递的值都被读为NULL 我已经通过writeSQL()实现进行了跟踪,我可以看到它被调用并且所有值都正确地传递到Oracle代码中。 我试图在setObject()调用中使用Types.JAVA_OBJECT ,并收到错误: Invalid column type

[更新2]接近疯狂的无助我已经实现了OracleData模式

public class Record implements SQLData, OracleData, OracleDataFactory {
...
@Override
public Object toJDBCObject(Connection conn) throws SQLException {
    return conn.createStruct(getSQLTypeName(), new Object[] {
            Objects.isNull(owner_id) ? "" : owner_id,
            Objects.isNull(record_id) ? "" : record_id,
            Objects.isNull(ip) ? "0" : ip,
            Objects.isNull(prefix) ? "" : prefix,
            String.valueOf(port),
            Objects.isNull(description) ? "" : description,
            Objects.isNull(cost_id) ? "" : cost_id
    });
}

@Override
public OracleData create(Object jdbcValue, int sqltype) throws SQLException {
    if (Objects.isNull(jdbcValue)) return null;
    LinkedList<Object> attr = new LinkedList<>(Arrays.asList(((OracleStruct)jdbcValue).getAttributes()));
    Record r = new Record();
    r.setOwner_id(attr.removeFirst().toString());
    r.setRecord_id(attr.removeFirst().toString());
    r.setIp(attr.removeFirst().toString());
    r.setPrefix(attr.removeFirst().toString());
    r.setPort(Integer.parseInt(attr.removeFirst().toString()));
    r.setDescription(attr.removeFirst().toString());
    r.setCost_id(attr.removeFirst().toString());
    return r;
}

public static OracleDataFactory getOracleDataFactory() {
    return new Record();
}

来电代码:

...
// unwrap the Oracle object from C3P0 (standard JDBCv4 API)
OracleCallableStatement ops = call.unwrap(OracleCallableStatement.class);
// I'm not sure why I even need to do this - it looks exactly like
// the standard JDBC code
for (Records r : records) {
    ops.registerOutParameter(1, Types.VARCHAR);
    ops.setObject(2, t);
    ops.execute();
    ids.add(ops.getString(1));
}
...

同样,相同的结果 - 没有错误,在表中创建了一个记录,所有提供的值都为null。 我已经跟踪了代码,并且正确调用了toJDBCObject()方法,并将值正确传递给createStruct()

发现了问题。 令人讨厌的是,它的字符编码。

如果在toJDBCObject()实现中,我在创建的结构上运行getAttributes() ,生成的Object[]数组将所有字段设置为"???" 这很奇怪,看起来像字符集转码失败(虽然它看起来很奇怪 - 所有字段都有三个问号,无论值长度如何,包括空字符串值)。

根据Oracle的JDBC开发人员指南,“全球化支持”

基本Java Archive(JAR)文件ojdbc7.jar包含为以下各项提供完整全球化支持的所有必需类:

  • 未作为Oracle对象或集合类型的数据成员检索或插入的CHAR,VARCHAR,LONGVARCHAR或CLOB数据的Oracle字符集。

  • 对象和集合的CHAR或VARCHAR数据成员,用于字符集US7ASCII,WE8DEC,WE8ISO8859P1,WE8MSWIN1252和UTF8。

要在对象或集合的CHAR或VARCHAR数据成员中使用任何其他字符集,必须在CLASSPATH环境变量中包含orai18n.jar:

ORACLE_HOME/jlib/orai18n.jar

我的设置是使用字符集“ WE8ISO8859P9 ”(我不知道为什么,它意味着什么,或者即使它是由客户端或服务器选择的 - 我只是转储了由OracleData API实现创建的STRUCT对象,它是在某处)。

因此,当Oracle表示它不“ 提供完整的全球化支持 ”时,它们意味着“ 所有字符字段将被静默转换为NULL ”。 Hmpph。

无论如何,将orai18n.jar添加到CLASSPATH确实解决了问题,现在记录正确地添加到数据库中。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM