2012-07-19 43 views
7

我们试图将一个UTF-16编码的字符串存储到AL32UTF8 Oracle数据库中。Oracle JDBC字符集和4000字符限制

我们的程序在使用WE8MSWIN1252作为字符集的数据库上完美工作。当我们尝试在使用AL32UTF8的数据库上运行它时,它会得到一个java.sql.SQLException: ORA-01461: can bind a LONG value only for insert into a LONG column

在下面的测试案例中,只要我们的输入数据不会太长,一切正常。

输入字符串可以超过4000个字符。我们希望保留尽可能多的信息,尽管我们意识到必须切断投入。

我们的数据库表使用CHAR关键字定义(见下文)。我们希望这可以让我们存储多达4000个字符的任何字符集。 可以这样做吗?如果是这样,怎么样?

我们尝试将字符串转换为UTF8使用ByteBuffer未成功。 OraclePreparedStatement.setFormOfUse(...)也没有帮助我们。

切换到CLOB不是一个选项。如果字符串太长,需要剪切。

这是我们的时刻代码:

public static void main(String[] args) throws Exception { 
    String ip ="193.53.40.229"; 
    int port = 1521; 
    String sid = "ora11"; 
    String username = "obasi"; 
    String password = "********"; 

    String driver = "oracle.jdbc.driver.OracleDriver"; 
    String url = "jdbc:oracle:thin:@" + ip + ":" + port + ":" + sid; 
    Class.forName(driver); 

    String shortData = ""; 
    String longData = ""; 
    String data; 

    for (int i = 0; i < 5; i++) 
     shortData += "é"; 

    for (int i = 0; i < 4000; i++) 
     longData += "é"; 

    Connection conn = DriverManager.getConnection(url, username, password); 

    PreparedStatement stat = null; 
    try { 
     stat = conn.prepareStatement("insert into test_table_short values (?)"); 
     data = shortData.substring(0, Math.min(5, shortData.length())); 
     stat.setString(1, data); 
     stat.execute(); 

     stat = conn.prepareStatement("insert into test_table_long values (?)"); 
     data = longData.substring(0, Math.min(4000, longData.length())); 
     stat.setString(1, data); 
     stat.execute(); 
    } finally { 
     try { 
      stat.close(); 
     } catch (Exception ex){} 
    } 
} 

这是简单的表的创建脚本:

CREATE TABLE test_table_short (
    DATA VARCHAR2(5 CHAR); 
); 

CREATE TABLE test_table_long (
    DATA VARCHAR2(4000 CHAR); 
); 

测试用例完美的作品在短数据。然而,在长数据中,它一直在收到错误。即使我们的longData只有3000个字符长,它仍然不能成功执行。

在此先感谢!

回答

7

在Oracle 12.1之前,VARCHAR2列仅限于在数据库字符集中存储4000字节的数据,即使它声明为VARCHAR2(4000 CHAR)。由于字符串中的每个字符都需要UTF-8字符集中的2个字节的存储空间,因此您无法在列中存储超过2000个字符。当然,如果某些字符实际上只需要1个字节的存储空间,或者其中一些字符需要多于2个字节的存储空间,那么这个数字将会改变。当数据库字符集为Windows-1252时,字符串中的每个字符只需要一个字节的存储空间,因此您可以在列中存储4000个字符。

既然你有更长的字符串,是否可以宣布该列为CLOB而不是VARCHAR2?这将(有效地)移除长度限制(对于取决于Oracle版本和块大小的CLOB的大小有限制,但它至少在多GB范围内)。

如果你碰巧使用Oracle 12.1或更高版本,该max_string_size参数允许您increase the maximum size of a VARCHAR2 column from 4000 bytes to 32767 bytes

+0

谢谢你的回答。可悲的是,在这种情况下,使用clob's对我们来说是不可能的。根据[链接](https://forums.oracle.com/forums/thread.jspa?threadID=2369974)这是正确的答案。然而,[谦逊的意见] [链接](http://stackoverflow.com/questions/81448/difference-between-byte-and-char-in-column-datatypes)是相当误导。你知道这是在文档中解释吗?我们一直在寻找很多,但找不到这个。 – Arolition 2012-07-19 14:51:47

+0

@Arolition - 我给SO线程添加了一条评论。就目前而言,答案是正确的。它只是没有注意到,如果一个特定的4000个字符需要超过4000个字节的存储空间,那么4000字节的容量限制仍然会启动。 – 2012-07-19 14:59:45

+1

UTF-8是一种可变长度编码。许多亚洲字符需要至少三个字节进行编码。 – 2014-03-14 15:14:23

4

通过将字符串切割为需要的字节长度解决了此问题。请注意,这不能简单地使用

stat.substring(0, length) 

,因为这会产生一个UTF-8字符串,最多可能会超过允许的三倍来完成。

while (stat.getBytes("UTF8").length > length) { 
    stat = stat.substring(0, stat.length()-1); 
} 

注意不要使用stat.getBytes(),因为这是依赖于一套 '的file.encoding',产生的是Windows 1252或UTF-8字节!

如果你使用Hibernate,你可以使用org.hibernate.Interceptor来做到这一点!