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

时间:2016-09-14 17:26:20

标签: java oracle jdbc java-8

我使用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代码中。我已尝试在Types.JAVA_OBJECT来电中使用setObject(),但收到错误:Invalid column type

[更新2] 接近疯狂的无助,我实施了OracleData pattern

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

1 个答案:

答案 0 :(得分:0)

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

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

根据Oracle's JDBC developer guide, "Globalization Support"

  

基本Java Archive(JAR)文件ojdbc7.jar,包含为以下内容提供全面全球化支持的所有必需类:

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

    < / LI>   
  • 对象的CHAR或VARCHAR数据成员以及字符集US7ASCII,WE8DEC,WE8ISO8859P1,WE8MSWIN1252和UTF8的集合。

  •   
     

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

     

ORACLE_HOME/jlib/orai18n.jar

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

因此,当Oracle表示它不会提供完整的全球化支持时,他们就意味着&#34; 所有字符字段都将被静默转换为{{ 1}} &#34 ;. Hmpph。

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