【问题标题】:Strange Oracle XMLType.getClobVal() result奇怪的 Oracle XMLType.getClobVal() 结果
【发布时间】:2012-11-23 18:35:51
【问题描述】:

我使用 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) );

阅读器返回完整的 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();
}

-------- 编辑。简单测试--------

我将使用从您的答案中学到的所有知识。创建最简单的表:

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 中,而不是在第二个中:

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

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

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

BR, 杰米

【问题讨论】:

  • 您可以使用utl_file 将其写入文件并查看内容的样子吗?你也可以试试select XMLType.getClobVal(PROJECT) from PROJECTS;吗? (虽然功能上没有什么不同)
  • 列是如何填充的?您确定问题出在检索上吗?如果不同的客户看到相同的东西,这听起来不太可能。您还可以选择该值的子串并查看空字符是否仍在其中。
  • 我跑了utlfile.sql和prvtfile.plb,还是不能使用utl_file(ORA-06521: PL/SQL: Error mapping function),对不起。
  • 我使用了 XMLType.getClobVal(PROJECT) 并且有空字符。然后我使用了 T1.PROJECT.getBlobVal(nls_charset_id('AL32UTF8')) 并且下载的 blob 中没有空字符(使用 SQL Developer 或 Java)。
  • @Alex 我编辑了我的帖子。似乎确实 null 不存在,因为不可能以这种方式填充 XMLTYPE 列。看来问题出在检索上。

标签: oracle xmltype


【解决方案1】:

这不是 Oracle 错误(它可以很好地存储和检索 \0。这是一个客户端/Windows 错误(不同客户端在“NUL”方面的行为与 Windows 一样)

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>

然而蟾蜍在这方面失败了:

如您所见,sql developer 的表现更好:

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

在使用 sql developer/windows 剪贴板时,您只需 replace(T1.PROJECT.getClobVal(), chr(0), null) 即可解决此问题。

【讨论】:

  • 感谢您的回复。当然剪贴板是 Windows 的问题,但这不是主要问题,它只是一种测试它的方法。我想知道为什么 null 存在:)。我认为这不是客户的问题。当我从这个表(使用 SQL Developer 或 Java 中的 Blob)下载单个单元格时,没有空字符。它仅在我使用 PROJECT.getClobVal() (在 SQL Developer 和 Java 中)时出现。所以对我来说,getClobVal() 在结果的 4000 位置插入空字符是接缝。
  • 不,它不这样做(或不应该这样做),如果是这样,在每个 LOB 上,我建议您向 Oracle 提交一份 SR。我在超过 1MB 的文件上广泛使用 XMLType,并且它从未在其中删除 chr(0) (10.2.0.4 + 11.2.0.2)。
  • 谢谢。我也使用 11.2.02 (Express Edition)。我用最简单的测试编辑了我的帖子。似乎每个 getClobVal() 结果在 PL/SQL 之外传输时都会出现问题。
  • 我已经使用 11.2.0.2.0 企业客户端 + 11.2.0.2.0 企业数据库进行了测试,并且根据您的测试用例,无法复制 4000 chr(0) 问题。使用的 sql developer 版本是 3.2.20.09。您是否尝试过使用另一台机器或重新安装您的客户端软件以查看是否可以解决问题?我在 oracle 支持站点上也看不到任何内容,但是由于您的 db 端测试显示没有 chr(0),我将从客户端开始(即您的 java 驱动程序和 oracle 客户端安装)
【解决方案2】:

我也遇到了与 Mikosz 所描述的完全相同的问题(在将我的 XMLType 值输出为 Clob 时,在第 4000 个字符周围看到一个额外的“NUL”字符)。在玩 SQLDeveloper 时,我注意到一个有趣的解决方法。我试图查看我的 XMLType 的输出,但厌倦了滚动到第 4000 个字符,所以我开始将 Clob 输出包装在 substr(...) 中。令我惊讶的是,这个问题实际上消失了。我将其合并到我的 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"));
  ...
}

【讨论】:

  • 另外,值得一提的是,我们在几个地方使用了 XMLType 列。在我们注册了架构(结构化 xml)的地方,我没有看到这个问题。对于未使用注册模式的 XMLType 列,我们确实看到了这个问题。
  • 非常感谢您的建议。它也解决了我的问题。这是我唯一能找到合适解决方案的地方。
【解决方案3】:

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

还有另一个解决方案 如果读取为 blob,则不会出现类似

的错误
T1.PROJECT.getBlobVal(nls_charset_id('UTF8'))

【讨论】:

    【解决方案4】:

    很容易验证它是否是 .getClobVal() 调用 - 在 PL/SQL(不是 Java)中对生成的 CLOB 执行 INSTR 测试以查看 CHR(0) 是否存在。

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

    【讨论】:

    • 谢谢。你是对的,在 PL/SQL 中没有空值。结果传到外面的时候出现。这很奇怪,因为我使用了两个不同的客户端(Windows 上的 SQL Developer 和 Red Hat 上的 Java)。
    • 您可能遇到了 JDBC 错误;看到你的两个客户端都是基于 Java 的。
    猜你喜欢
    • 2019-06-10
    • 2012-01-17
    • 1970-01-01
    • 1970-01-01
    • 2012-09-17
    • 2016-11-28
    • 2012-12-16
    • 2012-12-06
    • 2016-08-24
    相关资源
    最近更新 更多