我使用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()
。
答案 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
确实解决了问题,现在记录正确地添加到数据库中。