我使用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 utils-我喜欢它们的bean流操作。*连接使用的是C3P0连接池--这可能是相关的吗?*只是为了说明--并不是bean处理器将空值填充到Record
bean字段--如果我使用see直接加载表(或视图),则可以看到数据库中的字段确实设置为NULL
。
进程运行时不存在SQLException
,或者任何其他注意事项都是错误的。
有什么好查的吗?
更新在阅读了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濒临疯狂无助我已经实现了模式
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()
。
发布于 2016-09-16 14:51:52
发现问题了。令人烦恼的是,它是关于字符编码的。
如果在toJDBCObject()
实现中,我在创建的结构上运行getAttributes()
,则生成的Object[]
数组的所有字段都设置为"???"
。这很奇怪,看起来像是字符集转码失败(尽管看起来也很奇怪--所有字段都有三个问号,不管值长如何,包括空字符串值)。
基本Java Archive (JAR)文件ojdbc7.jar包含所有必要的类,以便为以下方面提供完全的全球化支持:
若要在对象或集合的CHAR或VARCHAR数据成员中使用任何其他字符集,必须在CLASSPATH环境变量中包括orai18n.jar:
ORACLE_HOME/jlib/orai18n.jar
我的设置是使用字符集"WE8ISO8859P9
“(我不知道为什么、它意味着什么,或者即使它是由客户机或服务器选择的--我只是转储了由OracleData
API实现创建的STRUCT
对象,它就在那里)。
因此,当甲骨文说它没有“提供完全的全球化支持”时,就意味着“所有字符字段都将被悄悄地转换为NULL
”。嗯哼。
无论如何,将orai18n.jar
添加到CLASSPATH
确实解决了问题,现在记录被正确地添加到数据库中。
https://stackoverflow.com/questions/39496367
复制相似问题