Java String.substring()与Oracle 11g列VARCHAR2大小之间的字符串长度定义不一致

时间:2015-11-09 16:09:47

标签: java oracle hibernate oracle11g

我用这样的表设置我的数据库:

CREATE TABLE t_audit_log
(
  description VARCHAR2 (2500)
);

在使用它的Java应用程序中,我使用Hibernate将数据类映射到它,并确保我不会生成SQLExceptions,我将此截断算法放在属性getter中:

private static final int MAX_STRING_LEN_2500 = 2499;

public void setDescription(final String newDescription) {
    if (newDescription != null
        && newDescription.length() > MAX_STRING_LEN_2500) {
        description = newDescription.substring(0, MAX_STRING_LEN_2500);
    } else {
        description = newDescription;
    }
}

对于成千上万的审计日志条目,这种方法运行良好 - 直到今天。我在日志中找到了这个:

Nov 09, 2015 7:54:40 AM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
WARN: SQL Error: 12899, SQLState: 72000
Nov 09, 2015 7:54:40 AM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
ERROR: ORA-12899: value too large for column "BLABLA"."T_AUDIT_LOG"."DESCRIPTION" 
    (actual: 2501, maximum: 2500)

为什么substring()在值中留下了额外的字符?

2 个答案:

答案 0 :(得分:9)

怀疑您的数据库设置被设置为使用"byte semantics"进行长度操作(这是NLS_LENGTH_SEMANTICS的默认设置),在这种情况下,您说的是您希望字段在编码时长度最多为2500字节,而不是2500个字符。假设您的数据库使用UTF-8对字符串进行编码 - 如果您的字符串包含2498个ASCII字符和1个字符U + 20A0(欧元符号),则会产生总共2501个字节,但只有2499个字符。

Java length()substring()操作将按照UTF-16代码单元运行 - 这些代码单元可能非常与"字符语义对齐&#34 34 ;. (你不太可能尝试在基本多语种平面之外存储字符,这是单个字符需要两个UTF-16代码单元的地方,但它是可能的。)

你真的需要找出你希望字段长度实际表示的内容 - 然后你可以弄清楚是否要改变你在Java中执行截断的方式。

答案 1 :(得分:7)

修改您的Oracle NLS_LENGTH_SEMANTICS,如果您使用的是BYTE或CHAR,则不指定,默认为BYTE。某些字符可能需要一个字节才能存储到数据库中,因此请尝试将表修改为

CREATE TABLE t_audit_log
(
  description VARCHAR2 (2500 char)
);

再试一次。

<小时/>

来自Oracle docs

  

NLS_LENGTH_SEMANTICS的会话级别值指定默认值   用于VARCHAR2和CHAR表列的长度语义,   用户定义的对象属性和数据库中的PL / SQL变量   在会话中创建的对象。 此默认值可能会被覆盖   显式长度语义限定符BYTE和CHAR in列,   属性和变量定义