无法使用Hibernate/PostgreSQL将Euro-sign存储到LOB String属性中

Fra*_*ijn 10 postgresql hibernate lob character-encoding

我无法在PostgreSQL 8.4中使用Hibernate 3.6.10将特殊字符(例如欧元符号(€))写入LOB字符串属性.

我所知道的是PostgreSQL提供了两种不同的方法来将大字符对象存储在表的一列中.它们可以直接存储到该表列中,也可以间接存储在单独的表中(实际上称为pg_largeobject).在后一种情况下,该列保存对pg_largeobject中行的引用(OID).

Hibernate 3.6.10中的默认行为是间接OID方法.但是,可以在Lob属性中添加额外的注释@ org.hibernate.annotations.Type(type ="org.hibernate.type.TextType")以获取直接存储行为.

两种方法都可以正常工作,除了我想要使用特殊字符(如欧元符号(€))的那一刻.在这种情况下,直接存储机制继续工作,但间接存储机制中断.

我想以一个例子来证明这一点.我创建了一个带有2个@Lob属性的测试实体.一个遵循直接存储原则,另一个遵循间接存储:

@Basic
@Lob
@Column(name = "CLOB_VALUE_INDIRECT_STORAGE", length = 2147483647)
public String getClobValueIndirectStorage()
Run Code Online (Sandbox Code Playgroud)

@Basic
@Lob
@org.hibernate.annotations.Type(type="org.hibernate.type.TextType")
@Column(name = "CLOB_VALUE_DIRECT_STORAGE", length = 2147483647)
public String getClobValueDirectStorage()
Run Code Online (Sandbox Code Playgroud)

如果我创建一个实体,使用欧元符号填充这两个属性,然后将其保存到数据库中当我执行SELECT时看到以下内容我看到

 id | clob_value_direct_storage | clob_value_indirect_storage
----+---------------------------+----------------------------
  6 | €                         | 910579                     
Run Code Online (Sandbox Code Playgroud)

如果我然后查询表pg_largeobject我看到:

  loid  | pageno | data
--------+--------+------
 910579 |      0 | \254
Run Code Online (Sandbox Code Playgroud)

pg_largeobject的'data'列的类型为bytea,这意味着信息存储为原始字节.表达式'\ 254'表示一个单字节,而UTF-8表示字符'¬'.这正是我从数据库加载实体时得到的值.

UTF-8中的欧元符号由3个字节组成,因此我希望'data'列有3个字节而不是1个字节.

这不仅适用于欧元符号,也适用于许多特殊字符.这是Hibernate中的问题吗?还是JDBC驱动程序?有没有办法可以调整这种行为?

在此先感谢,
亲切的问候,
Franck de Bruijn

Fra*_*ijn 5

经过大量的Hibernate源代码和PostgreSQL JDBC驱动程序的挖掘后,我设法找到了问题的根本原因.最后,调用BlobOutputStream的write()方法(由JDBC驱动程序提供)以将Clob的内容写入数据库.此方法如下所示:

public void write(int b) throws java.io.IOException
{
    checkClosed();
    try
    {
        if (bpos >= bsize)
        {
            lo.write(buf);
            bpos = 0;
        }
        buf[bpos++] = (byte)b;
    }
    catch (SQLException se)
    {
        throw new IOException(se.toString());
    }
}
Run Code Online (Sandbox Code Playgroud)

此方法将'int'(32位/ 4字节)作为参数,并将其转换为'byte'(8位/ 1字节),有效地丢失3个字节的信息.Java中的字符串表示形式为UTF-16编码,这意味着每个字符由16位/ 2字节表示.欧元符号具有int值8364.在转换为字节之后,值172保持(以八位位组表示254).

我不确定现在最好的解决办法是解决这个问题.恕我直言,JDBC驱动程序应负责将Java UTF-16字符编码/解码为数据库所需的任何编码.但是,我没有看到JDBC驱动程序代码中有任何调整可能会改变它的行为(我不想编写和维护我自己的JDBC驱动程序代码).

因此,我使用自定义ClobType扩展了Hibernate,并设法在写入数据库之前将UTF-16字符转换为UTF-8,反之亦然,在检索Clob时.

在这个答案中,解决方案太大而不能简单粘贴.如果您有兴趣,请给我发一条线,然后发给您.

干杯,弗兰克