Невозможно сохранить знак евро в свойстве LOB String с Hibernate / PostgreSQL - PullRequest
10 голосов
/ 03 апреля 2012

У меня проблемы с записью и чтением специальных символов, таких как знак евро (€), в свойства LOB String в PostgreSQL 8.4 с Hibernate 3.6.10.

Что я знаю, так это то, что PostgreSQL предоставляет два разных способа хранения больших символьных объектов в столбце таблицы. Они могут храниться либо непосредственно в этом столбце таблицы, либо косвенно в отдельной таблице (на самом деле она называется pg_largeobject). В последнем случае столбец содержит ссылку (OID) на строку в pg_largeobject.

Поведение по умолчанию в Hibernate 3.6.10 - это косвенный подход OID. Однако можно добавить дополнительную аннотацию @ org.hibernate.annotations.Type (type = "org.hibernate.type.TextType") к свойству Lob для получения прямого поведения при хранении.

Оба подхода работают нормально, за исключением того момента, когда я хочу работать со специальными символами, такими как знак евро (€). В этом случае механизм прямого хранения продолжает работать, но механизм косвенного хранения выходит из строя.

Я бы хотел продемонстрировать это на примере. Я создал тестовую сущность с 2 свойствами @Lob. Один следует принципу прямого хранения, другой - косвенному:

@Basic
@Lob
@Column(name = "CLOB_VALUE_INDIRECT_STORAGE", length = 2147483647)
public String getClobValueIndirectStorage()

и

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

Если я создаю сущность, заполняю оба свойства знаком евро, а затем сохраняю его в базе данных. Когда я выбираю SELECT, я вижу следующее.

 id | clob_value_direct_storage | clob_value_indirect_storage
----+---------------------------+----------------------------
  6 | €                         | 910579                     

Если я затем запрашиваю таблицу pg_largeobject, то вижу:

  loid  | pageno | data
--------+--------+------
 910579 |      0 | \254

Столбец «data» в pg_largeobject имеет тип bytea, что означает, что информация хранится в виде необработанных байтов. Выражение «\ 254» представляет один байт, а в UTF-8 представляет символ «¬». Это именно то значение, которое я получаю при загрузке объекта обратно из базы данных.

Знак евро в UTF-8 состоит из 3 байтов, поэтому я ожидал, что столбец «data» будет иметь 3 байта, а не 1.

Это происходит не только для знака евро, но и для многих специальных символов. Это проблема в Hibernate? Или драйвер JDBC? Есть ли способ, которым я могу настроить это поведение?

Заранее спасибо,
С уважением,
Франк де Брюйн

1 Ответ

5 голосов
/ 04 апреля 2012

После долгих поисков в исходном коде Hibernate и JDBC-драйвере PostgreSQL мне удалось найти причину проблемы. В конце вызывается метод write () объекта BlobOutputStream (предоставляемый драйвером 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());
    }
}

Этот метод принимает значение int (32 бита / 4 байта) в качестве аргумента и преобразует его в байт (8 бит / 1 байт), эффективно теряя 3 байта информации. Строковые представления в Java кодируются в UTF-16, что означает, что каждый символ представлен 16 битами / 2 байта. Знак евро имеет значение int 8364. После преобразования в байт остается значение 172 (в представлении октетов 254).

Я не уверен, какое сейчас лучшее решение этой проблемы. ИМХО драйвер JDBC должен отвечать за кодирование / декодирование символов Java UTF-16 в соответствии с любой кодировкой, необходимой для базы данных. Однако в коде драйвера JDBC я не вижу возможностей для изменения его поведения (и я не хочу писать и поддерживать свой собственный код драйвера JDBC).

Поэтому я расширил Hibernate с помощью пользовательского ClobType и смог преобразовать символы UTF-16 в UTF-8 перед записью в базу данных и наоборот при извлечении Clob.

Решения слишком велики, чтобы просто вставить в этот ответ. Если вам интересно, напишите мне, и я пришлю вам.

Cheers, Franck

...