Oracle 12.2 数据库中 CLOB 的 JDBC 流给出错误的编码

Posted

技术标签:

【中文标题】Oracle 12.2 数据库中 CLOB 的 JDBC 流给出错误的编码【英文标题】:JDBC Stream of CLOB in Oracle 12.2 database gives wrong encoding 【发布时间】:2018-07-19 06:35:20 【问题描述】:

我在从 Oracle 12.1 迁移到 Oracle 12.2 数据库时遇到问题。 很明显,CLOB 列的编码在升级后会有所不同。

我正在使用 JDBC8 进行数据库连接。

已执行以下数据库脚本:

create table oracle_compatibility_test
(
    id           number(19)    not null primary key,
    name         varchar2(500) not null,
    name_as_clob clob          not null
);    

insert all
    into oracle_compatibility_test (id, name, name_as_clob) values (1, 'test1', 'test1')
    into oracle_compatibility_test (id, name, name_as_clob) values (2, 'test2äößrt', 'test2äößrt')
select *
    from dual;

commit;

然后我编写了一个程序来通过 JDBC 读取插入的值并将它们打印到控制台:

package de.pie;

import java.io.IOException;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

public final class OracleCompatibilityTest 
    //A 12.2 Oracle Database
    private static final String DB1 = "jdbc:oracle:thin:@server1:1521";
    //A 12.1 Oracle Database
    private static final String DB2 = "jdbc:oracle:thin:@server2:1521";

    private static final String USED_DATABASE = DB1;

    public static void main(String[] args) 
        printDatabaseCharset();

        try (Connection connection = DriverManager.getConnection(USED_DATABASE, databaseProperties());
             Statement statement = connection.createStatement();
             ResultSet result = statement.executeQuery(
                     "select name, name_as_clob, convert(name_as_clob, 'AL32UTF8') as utf_8_clob " +
                             "from oracle_compatibility_test " +
                             "order by id asc")) 
            readRow(result);
            readRow(result);
         catch (Exception e) 
            throw new RuntimeException(e);
        
    

    private static void printDatabaseCharset() 
        try (Connection connection = DriverManager.getConnection(USED_DATABASE, databaseProperties());
             Statement statement = connection.createStatement();
             ResultSet result = statement.executeQuery(
                     "select * from database_properties where property_name = 'NLS_CHARACTERSET'")) 
            result.next();
            System.out.println("read charset from database: " + result.getString("property_value") + "\n");
         catch (Exception e) 
            throw new RuntimeException(e);
        
    

    private static void readRow(ResultSet result) throws SQLException, IOException 
        result.next();

        String name = result.getString("name");
        String nameAsClob = result.getString("name_as_clob");
        Clob clobAsClobObject = result.getClob("name_as_clob");
        String clobObjectAsString = clobAsClobObject.getSubString(1, (int) clobAsClobObject.length());
        String clobReadAsCharacterStream = readFromCharacterStream(result);
        String utf8clob = result.getString("utf_8_clob");


        StringBuilder sb = new StringBuilder();
        sb.append("read name: ")
          .append(name)
          .append("\nname read as clob: ")
          .append(nameAsClob)
          .append("\nname read as clob-object: ")
          .append(clobObjectAsString)
          .append("\nclob read as character-stream: ")
          .append(clobReadAsCharacterStream)
          .append("\nclob converted to utf-8: ")
          .append(utf8clob)
          .append("\n\n\n");

        System.out.println(sb.toString());
    

    private static String readFromCharacterStream(ResultSet result) throws SQLException, IOException 
        try (Reader reader = result.getCharacterStream("name_as_clob")) 
            StringBuilder stringBuilder = new StringBuilder();
            int c;
            while ((c = reader.read()) != -1) 
                stringBuilder.append((char) c);
            
            return stringBuilder.toString();
        
    

    private static Properties databaseProperties() 
        Properties prop = new Properties();
        prop.put("user", "user");
        prop.put("password", "password");
        return prop;
    

Oracle 12.1 的输出如下:

read charset from database: WE8ISO8859P15

read name: test1
name read as clob: test1
name read as clob-object: test1
clob read as character-stream: test1
clob converted to utf-8:  t e s t 1



read name: test2äößrt
name read as clob: test2äößrt
name read as clob-object: test2äößrt
clob read as character-stream: test2äößrt
clob converted to utf-8:  t e s t 2 ä ö ß r t

使用 Oracle 12.2 的输出如下:

read charset from database: WE8ISO8859P15

read name: test1
name read as clob: test1
name read as clob-object: test1
clob read as character-stream: test1
clob converted to utf-8:  t e s t 1



read name: test2äößrt
name read as clob: test2���rt
name read as clob-object: test2���rt
clob read as character-stream: test2���rt
clob converted to utf-8:  t e s t 2 � � � r t

JDBC-Driver 错误地将字符集自动检测为 UTF-8,但 Stream 确实是 ISO8859-15。无法在 JDBC8 中显式设置字符集。 从数据库返回的 Stream 在 Oracle 12.1 下以 UTF-8 编码

【问题讨论】:

也许看看docs.oracle.com/database/121/JJDBC/global.htm#JJDBC28643 实际上,如果你在两个具有相同字符集的数据库上运行相同的 Java 程序并遇到这个问题真的很奇怪。 我觉得奇怪的是,相同的 JDBC 驱动程序可以在 12.1 上正常工作,然后在 12.2 上失败。事实上,由于驱动程序没有改变,但您的数据库发生了变化,因此问题更可能出在数据库上(这当然会导致驱动程序以这种方式行事)。 我在 Oracle 上打开了一个服务请求 【参考方案1】:

我们发现了问题并找到了解决方案(这可能更像是一种解决方法)。

在 Oracle 12.1 下,属性 oracle.jdbc.defaultLobPrefetchSize 默认设置为 -1。在 Oracle 12.2 下,这更改为 4000,这意味着数据库将尝试获取一个查询中的所有内容并将 CLOB 转换为 VARCHAR2(如果其大小低于 4000,请参阅here)。在使用 Charset WE8ISO8859P15 时,这在某种程度上不适用于 Oracle 12.2。

将此属性设置为 -1 将再次禁用预取(因此需要另一个数据库访问来获取 CLOB 列)并且一切工作正常。 p>

在 Oracle 12.1 下,使用相同的编码并将 defaultLobPrefetchSize 显式设置为 4000 可以正常工作。所以这显然是数据库端的一个错误。

【讨论】:

这似乎仍然是 Oracle 19c 相同问题的解决方案 对于那些想知道如何为 JBoss 设置此属性的人:只需将其添加为像这样的系统属性

以上是关于Oracle 12.2 数据库中 CLOB 的 JDBC 流给出错误的编码的主要内容,如果未能解决你的问题,请参考以下文章

在 Oracle 中使用 SQL 操作 JSON 数据

oracle中怎样修改varchar2字段为clob字段

oracle中blob,clob,nclob主要区别是啥?

oracle如何操作clob数据类型

在oracle查询clob字段的内容怎么办

oracle中LOBSEGMENT类型存储的clob的内容过大,如何清除clob字段内容。释放表空间!