简体   繁体   English

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

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

I'm using JDBC with createStruct() to call a stored procedure on an Oracle database that accepts a custom type as a parameter. 我正在使用JDBC和createStruct()来调用Oracle数据库上的存储过程,该数据库接受自定义类型作为参数。 The stored procedure inserts the custom type fields into a table and when I SELECT from the table later I see that all the fields that I tried to insert are NULL . 存储过程将自定义类型字段插入到表中,当我稍后从表中SELECT ,我看到我尝试插入的所有字段都是NULL

The custom type looks like this: 自定义类型如下所示:

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

The stored procedure looks like this: 存储过程如下所示:

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;

My Java code looks something like this: 我的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());
}

Notes: * That last part is using Apache Commons db-utils - I love their bean stream operations. 注意:*最后一部分是使用Apache Commons db-utils - 我喜欢他们的bean流操作。 * The connection is using C3P0 connection pool - could that be related? *连接使用C3P0连接池 - 可能是相关的吗? * Just to make it clear - its not that the bean processor populates null values into the Record bean fields - if I use an SQL explorer to load the table (or view) directly, I can see that the fields in the database are indeed set to NULL . *只是为了说清楚 - 它不是bean处理器将空值填充到Record bean字段中 - 如果我使用SQL资源管理器直接加载表(或视图),我可以看到数据库中的字段确实已设置为NULL

There are no SQLException s when the process runs, or any other notice that something is wrong. 进程运行时没有SQLException ,或者有任何其他错误通知。

Any ideas what to check? 任何想法要检查什么?

[Update] After reading on Oracle Objects and SQLData mappings, I rewrote the code to use SQLData . [更新]在阅读了Oracle Objects和SQLData映射之后,我重新编写了使用SQLData的代码。

The Record class now implements SQLData and it's writeSQL() method looks like this: 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);
}

Then at the start of the calling code, I've added: 然后在调用代码的开头,我添加了:

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

And instead of using createStruct() , the setObject() call now looks simply like this: 而不是使用createStruct()setObject()调用现在看起来像这样:

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

But the result is the same - no errors and all the passed values are read as NULL . 但结果是相同的 - 没有错误,所有传递的值都被读为NULL I've traced through the writeSQL() implementation and I can see that it is called and all values are passed correctly into the Oracle code. 我已经通过writeSQL()实现进行了跟踪,我可以看到它被调用并且所有值都正确地传递到Oracle代码中。 I've tried to use Types.JAVA_OBJECT in the setObject() call, and got an error: Invalid column type . 我试图在setObject()调用中使用Types.JAVA_OBJECT ,并收到错误: Invalid column type

[Update 2] Bordering on insane helplessness I've implemented the OracleData pattern : [更新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();
}

Calling code: 来电代码:

...
// 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));
}
...

And again, same result - no errors, a record is created in the table, with all provided values are null. 同样,相同的结果 - 没有错误,在表中创建了一个记录,所有提供的值都为null。 I've traced through the code and the toJDBCObject() method is called correctly and does pass the values correctly in to createStruct() . 我已经跟踪了代码,并且正确调用了toJDBCObject()方法,并将值正确传递给createStruct()

Found the problem. 发现了问题。 Annoyingly, its about character encoding. 令人讨厌的是,它的字符编码。

If in the toJDBCObject() implementation, I run getAttributes() on the created struct, the resulting Object[] array has all fields set as "???" 如果在toJDBCObject()实现中,我在创建的结构上运行getAttributes() ,生成的Object[]数组将所有字段设置为"???" . Which is weird and looks like a character set transcoding failure (although it looks weird for that too - has three question marks for all fields regardless of value length, including empty string values). 这很奇怪,看起来像字符集转码失败(虽然它看起来很奇怪 - 所有字段都有三个问号,无论值长度如何,包括空字符串值)。

According to Oracle's JDBC developer guide, "Globalization Support" : 根据Oracle的JDBC开发人员指南,“全球化支持”

The basic Java Archive (JAR) file ojdbc7.jar, contains all the necessary classes to provide complete globalization support for: 基本Java Archive(JAR)文件ojdbc7.jar包含为以下各项提供完整全球化支持的所有必需类:

  • 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. 未作为Oracle对象或集合类型的数据成员检索或插入的CHAR,VARCHAR,LONGVARCHAR或CLOB数据的Oracle字符集。

  • CHAR or VARCHAR data members of object and collection for the character sets US7ASCII, WE8DEC, WE8ISO8859P1, WE8MSWIN1252, and UTF8. 对象和集合的CHAR或VARCHAR数据成员,用于字符集US7ASCII,WE8DEC,WE8ISO8859P1,WE8MSWIN1252和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: 要在对象或集合的CHAR或VARCHAR数据成员中使用任何其他字符集,必须在CLASSPATH环境变量中包含orai18n.jar:

ORACLE_HOME/jlib/orai18n.jar

And my setup was using the character set " WE8ISO8859P9 " (I have no idea why, what it means, or even if it is selected by the client or the server - I just dumped the STRUCT object created by the OracleData API implementation and it was there somewhere). 我的设置是使用字符集“ WE8ISO8859P9 ”(我不知道为什么,它意味着什么,或者即使它是由客户端或服务器选择的 - 我只是转储了由OracleData API实现创建的STRUCT对象,它是在某处)。

So when Oracle says that it does not " provide complete globalization support ", they mean " all character fields will be silently converted to NULL ". 因此,当Oracle表示它不“ 提供完整的全球化支持 ”时,它们意味着“ 所有字符字段将被静默转换为NULL ”。 Hmpph. Hmpph。

Anyway, adding orai18n.jar to the CLASSPATH indeed fixed the problem, and now records are added correctly to the database. 无论如何,将orai18n.jar添加到CLASSPATH确实解决了问题,现在记录正确地添加到数据库中。

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

相关问题 使用PL SQL表类型的参数对Oracle存储过程进行JDBC调用 - JDBC Call to Oracle Stored Procedure with parameters of type PL SQL table 如何调用Oracle PL-SQL过程,该过程具有自定义的复杂对象类型作为Java的输入参数 - How to call an Oracle PL-SQL procedure which has a custom defined complex Object type as input parameter from Java 使用JDBC将SYS_REFCURSOR作为IN参数调用PL / SQL过程 - Calling PL/SQL procedure with SYS_REFCURSOR as IN parameter using JDBC 使用简单的jdbc调用将数组作为输入参数传递给oracle存储过程 - Pass array as input parameter to an oracle stored procedure using simple jdbc call 使用简单的jdbc调用将数组作为输入参数传递给oracle存储过程 - Pass array as input parameter to an oracle stored procedure using simple jdbc call Oracle 11g:使用简单的 jdbc 调用将数组作为输入参数传递给 oracle 存储过程 - Oracle 11g: Pass array as input parameter to an oracle stored procedure using simple jdbc call Oracle PL / SQL存储过程中的JDBC结果集 - JDBC result set from an Oracle PL/SQL stored procedure 在SQL Server 2008中使用表类型输入参数调用存储过程 - Calling stored procedure with table type input parameter in sql server 2008 使用JDBC以用户定义的记录作为其IN参数调用PL / SQL过程 - Calling PL/SQL procedure with user defined record as its IN parameter using JDBC JDBC存储过程调用 - JDBC stored procedure call
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM