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

标签 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 处理器将 null 值填充到 Record bean 字段中 - 如果我使用 SQL 资源管理器直接加载表(或 View ),我可以看到数据库中的字段确实设置为 NULL

进程运行时不会出现SQLException,也不会出现任何其他错误提示。

有什么想法要检查吗?

[更新] 在阅读了 Oracle 对象和 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);

现在,setObject() 调用不再使用 createStruct(),而是如下所示:

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

但结果是相同的 - 没有错误,并且所有传递的值都被读取为 NULL。我跟踪了 writeSQL() 实现,可以看到它被调用并且所有值都正确传递到 Oracle 代码中。我尝试在 setObject() 调用中使用 Types.JAVA_OBJECT,但收到错误:无效的列类型

[更新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));
}
...

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

最佳答案

发现问题了。令人烦恼的是,它与字符编码有关。

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

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

The basic Java Archive (JAR) file ojdbc7.jar, contains all the necessary classes to provide complete globalization support for:

  • Oracle character sets for CHAR, VARCHAR, LONGVARCHAR, or CLOB data that is not being retrieved or inserted as a data member of an Oracle object or collection type.

  • CHAR or VARCHAR data members of object and collection for the character sets US7ASCII, WE8DEC, WE8ISO8859P1, WE8MSWIN1252, and UTF8.

To use any other character sets in CHAR or VARCHAR data members of objects or collections, you must include orai18n.jar in the CLASSPATH environment variable:

ORACLE_HOME/jlib/orai18n.jar

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

因此,当 Oracle 说它不“提供完整的全局化支持”时,他们的意思是“所有字符字段都将默默地转换为 NULL”。哼。

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

关于java - 使用 JDBC 调用自定义类型输入参数的 PL/SQL 存储过程,所有字段均为 null,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39496367/

相关文章:

java - 禁用 Eclipse Java Neon 中的错误提示/警告

java - 是否可以配置 Eclipse 以显示括号之间的线条?

c# - ORA-01460 : unimplemented or unreasonable conversion requested-uploading files

java - 数据源的自动提交是否应该设置为 false?

oracle - SQL 将数据从下划线分隔转换为 CamelCase

java - 是否可以调用绝对函数在 TYPE_FORWARD_ONLY 结果集中向前移动行?

java - 使用 JDBC 从 dbms_output.get_lines 获取输出

java - 为较低的 sdk/jdk 重建项目。失败 [INSTALL_FAILED_OLDER_SDK]

java - 如何在 JAVA 中创建发送图像的 HTTP 响应?

java - 在java数据库中使用select语句