奇怪的Oracle XMLType.getClobVal()结果

时间:2012-11-23 18:35:52

标签: oracle xmltype

我使用Oracle 11g(在Red Hat上)。我有XMLType列的简单常规表:

CREATE TABLE PROJECTS
(
  PROJECT_ID NUMBER(*, 0) NOT NULL,
  PROJECT SYS.XMLTYPE,
);

使用Oracle SQL Developer(在Windows上)我这样做:

select T1.PROJECT P1 from PROJECTS T1 where PROJECT_ID = '161';

有效。我得到一个牢房。我可以双击并下载整个XML文件。

然后我尝试将结果作为CLOB:

select T1.PROJECT.getClobVal() P1 from PROJECTS T1 where PROJECT_ID = '161';

有效。我得到一个牢房。我可以双击并查看全文并复制它。但有一个问题。当我将它复制到剪贴板时,我只得到前4000个字符。似乎位置4000处有0x00字符,而CLOB的其余部分未被复制。

为了证实这一点,我在java中写了支票:

// ... create projectsStatement
Reader reader = projectsStatement.getResultSet().getCharacterStream( "P1" );
BufferedReader bf = new BufferedReader( reader );
char buffer[] = new char[ 1024 ];
int count = 0;
int globalPos = 0;
while ( ( count = bf.read( buffer, 0, buffer.length ) ) > 0 )
    for ( int i = 0; i < count; i++, globalPos++ )
        if ( buffer[ i ] == 0 )
            throw new Exception( "ZERO at " + Integer.toString(globalPos) );

Reader返回完整的XML,但我的异常被抛出,因为位置4000处有空字符。我可以删除这个单字节,但这将是一个相当奇怪的解决方法。

我不在那里使用VARCHAR2但是这个问题可能与VARCHAR2限制(4000字节)有关吗?还有其他想法吗?这是Oracle的错误还是我错过了什么?

--------------------编辑--------------------

使用以下存储过程插入值:

create or replace
procedure addProject( projectId number, projectXml clob ) is
  sqlstr varchar2(2000);
begin

  sqlstr := 'insert into projects ( PROJECT_ID, PROJECT ) VALUES ( :projectId, :projectData )';
  execute immediate sqlstr using projectId, XMLTYPE(projectXml);

end;

用于调用它的Java代码:

try ( CallableStatement cs = connection.prepareCall("{call addProject(?,?)}") )
{
    cs.setInt( "projectId", projectId );
    cs.setCharacterStream( "projectXml", new StringReader(xmlStr) , xmlStr.length() );
    cs.execute();
}

--------------------编辑。 SIMPLE TEST --------------------

我会用你从答案中学到的一切。创建最简单的表:

create table T1 ( P XMLTYPE );

使用XML准备两个CLOB。首先是空字符,第二个没有。

declare
  P1 clob;
  P2 clob;
  P3 clob;
begin

  P1 := '<a>';
  P2 := '<a>';
  FOR i IN 1..1000 LOOP
    P1 := P1 || '0123456789' || chr(0);
    P2 := P2 || '0123456789';
  END LOOP;
  P1 := P1 || '</a>';
  P2 := P2 || '</a>';

检查null是否在第一个CLOB中而不在第二个CLOB中:

DBMS_OUTPUT.put_line( DBMS_LOB.INSTR( P1, chr(0) ) );
DBMS_OUTPUT.put_line( DBMS_LOB.INSTR( P2, chr(0) ) );

我们会按预期得到:

14
0

尝试将第一个CLOB插入XMLTYPE。不起作用。无法插入此值:

insert into T1 ( P ) values ( XMLTYPE( P1 ) );

尝试将第二个CLOB插入XMLTYPE。它会起作用:

insert into T1 ( P ) values ( XMLTYPE( P2 ) );

尝试将插入的XML读入第三个CLOB。它会起作用:

select T.P.getClobVal() into P3 from T1 T where rownum = 1;

检查是否有空。没有空:

DBMS_OUTPUT.put_line( DBMS_LOB.INSTR( P3, chr(0) ) );

它认为数据库中没有null,只要我们在PL / SQL上下文中,就没有null。但是当我尝试在SQL Developer(在Windows上)或在Java中(在Red Hat EE和Tomcat7上)使用以下SQL时,我在所有返回的CLOB中的位置4000处获得空字符:

select T.P.getClobVal() from T1 T;

BR, JM

4 个答案:

答案 0 :(得分:6)

它不是一个Oracle错误(它存储和检索\ 0就好了。它是一个客户端/窗口错误(不同的客户端在&#34; NUL和#34;以及窗口方面的行为方式不同)

chr(0)实际上不是非blob中的有效字符(我很好奇你是如何让XMLType首先接受它,因为它通常不会解析)。

\ 0在C中用于表示字符串的结尾(NUL终止符),并且某些GUI将在该点停止处理字符串。例如:

![SQL> select 'IM VISIBLE'||chr(0)||'BUT IM INVISIBLE'
  2  from dual
  3  /

'IMVISIBLE'||CHR(0)||'BUTIM
---------------------------
IM VISIBLE BUT IM INVISIBLE

SQL>

然而toad在这方面失败了: TOAD

sql developer更好,你可以看到它:

SQL Developer

但是如果你复制它,剪贴板只会将其复制到零字符。这个复制粘贴错误并不是SQL开发人员的错,但是它的问题是Windows剪贴板不允许NUL正确粘贴。

使用sql developer / windows clipboard时,你应该replace(T1.PROJECT.getClobVal(), chr(0), null)来解决这个问题。

答案 1 :(得分:3)

我也遇到了与Mikosz完全相同的问题(在输出我的XMLType值作为Clob时,在第4000个字符周围看到一个额外的'NUL'字符)。在SQLDeveloper中玩游戏时,我注意到了一个有趣的解决方法。我试图看到我的XMLType的输出,但厌倦了滚动到第4000个字符,所以我开始在一个substr(...)中包装Clob输出。令我惊讶的是,这个问题实际上已经消失了。我把它合并到我的Java应用程序中并确认问题不再存在,并且可以在没有额外字符的情况下检索我的Clob。我知道这不是一个理想的解决方法,而且我仍然不确定它为什么会起作用(如果有人可以向我解释的话会很乐意),但这里是我目前工作的缩写示例:

// Gets the xml contents
String sql = "select substr(x.xml_content.getClobVal(), 0) as xml_content from my_table x";
ps = con.prepareStatement(sql);
if(rs.next()) {
  Reader reader = new BufferedReader(rs.getCharacterStream("xml_content"));
  ...
}

答案 2 :(得分:3)

错误:14781609 XDB:当XML存储在CLOB中时,XMLType.getclobval()返回临时LOB。 修复补丁集11.2.0.4

和另一种解决方案 如果读为blob,则没有错误,如

T1.PROJECT.getBlobVal(nls_charset_id('UTF8'))

答案 3 :(得分:1)

很容易验证是否是.getClobVal()调用 - 在结果CLOB上执行PL / SQL(非Java)中的INSTR测试,以查看CHR(0)是否存在或不。

如果没有,那么我会指责你的Oracle客户端安装。