Невозможно сохранить знак Euro в свойство LOB String с помощью Hibernate/PostgreSQL
У меня возникли проблемы с написанием и чтением специальных символов, таких как знак Евро (€), в свойства 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, чтобы получить прямое поведение хранилища.
Оба подхода работают отлично, за исключением того, что я хочу работать со специальными символами, такими как знак Euro (€). В этом случае механизм прямого хранения продолжает работать, но механизм косвенного хранения прерывается.
Я хотел бы продемонстрировать это на примере. Я создал тестовый объект с 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()
Если я создаю объект, заполните оба свойства знаком Euro, а затем сохраняйте его в базе данных, я вижу следующее, когда я делаю 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 - символ "¬". Это именно то значение, которое я возвращаю, когда я загружаю объект обратно из базы данных.
Знак Euro в UTF-8 состоит из 3 байтов, поэтому я ожидал, что столбец "data" будет иметь 3 байта, а не 1.
Это происходит не только для знака Euro, но и для многих специальных символов. Это проблема в Hibernate? Или драйвер JDBC? Есть ли способ, которым я могу настроить это поведение?
Спасибо заранее,
С уважением,
Franck de Bruijn
Ответы
Ответ 1
После большого поиска в исходном коде Hibernate и драйвера PostgreSQL JDBC мне удалось найти основную причину проблемы. В итоге метод 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 байта. Знак Euro имеет значение int 8364. После преобразования в байт значение 172 остается (в октетном представлении 254).
Я не уверен, что сейчас самое лучшее решение этой проблемы. IMHO драйвер JDBC должен нести ответственность за кодирование/декодирование символов Java UTF-16 любой кодировке, необходимой для базы данных. Тем не менее, я не вижу возможности настройки в коде драйвера JDBC, чтобы изменить его поведение (и я не хочу писать и поддерживать свой собственный код драйвера JDBC).
Поэтому я расширил Hibernate с помощью пользовательского ClobType и смог преобразовать символы UTF-16 в UTF-8 перед записью в базу данных и наоборот при извлечении Clob.
Решения слишком велики, чтобы просто вставить этот ответ. Если вам интересно, напишите мне письмо, и я отправлю его вам.
Cheers,
Franck