2012-04-03 9 views
10

Mam problem z zapisaniem i odczytaniem znaków specjalnych, takich jak znak Euro (€), do właściwości LOB String w PostgreSQL 8.4 z Hibernate 3.6.10.Nie można zapisać znaku Euro we właściwości LOB String przy użyciu Hibernate/PostgreSQL

Wiem, że PostgreSQL zapewnia dwa różne sposoby przechowywania dużych obiektów znaków w kolumnie tabeli. Mogą być przechowywane bezpośrednio w tej kolumnie tabeli lub pośrednio w osobnej tabeli (faktycznie nazywa się to pg_largeobject). W tym drugim przypadku kolumna zawiera odniesienie (OID) do wiersza w pg_largeobject.

Domyślnym zachowaniem w Hibernate 3.6.10 jest pośrednie podejście OID. Możliwe jest jednak dodanie dodatkowej adnotacji @ org.hibernate.annotations.Type (type = "org.hibernate.type.TextType") do właściwości Lob, aby uzyskać zachowanie bezpośredniego przechowywania.

Oba podejścia działają dobrze, z wyjątkiem momentu, w którym chcę pracować ze znakami specjalnymi, takimi jak znak Euro (€). W takim przypadku mechanizm bezpośredniego zapisu działa, ale pośredni mechanizm magazynowania zostaje przerwany.

Chciałbym to zademonstrować na przykładzie. Stworzyłem jednostkę testową z 2 właściwościami @Lob. Jeden następuje bezpośredni zasady przechowywania, drugi przechowywanie pośrednie:

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

i

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

Jeśli utworzyć podmiot, wypełnić zarówno właściwości ze znakiem Euro i następnie utrzymywać w kierunku bazy widzę następujące kiedy zrobić SELECT widzę

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

Gdybym wtedy kwerendy pg_largeobject tabeli widać:

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

Kolumna "data" obiektu pg_largeobject ma typ bytea, co oznacza, że ​​informacje są przechowywane jako surowe bajty. Wyrażenie "\ 254" reprezentuje jeden pojedynczy bajt, a w UTF-8 oznacza znak "¬". Jest to dokładnie wartość, którą otrzymuję, gdy ładuję obiekt z bazy danych.

Znak Euro w UTF-8 składa się z 3 bajtów, więc spodziewałem kolumnie „dane”, aby mieć 3 bajty, a nie 1.

To nie tylko występować na znak Euro, ale dla wiele znaków specjalnych. Czy to jest problem w Hibernate? Lub sterownik JDBC? Czy jest sposób, w jaki mogę zmienić to zachowanie?

Dzięki z góry,
poważaniem,
Franck de Bruijn

+1

Dlaczego używasz dużych obiektów w pierwszej kolejności? Po prostu użyj typu danych "text" dla tej kolumny. Nie ma potrzeby robienia bałaganu z 'bytea' lub dużymi obiektami, jeśli wszystko, co chcesz zapisać, to tekst. –

+0

Może być wiele powodów, aby to zrobić. Nie wiem Zapewniam ramy dla innych użytkowników i chcę wspierać obie alternatywy. W starszych wersjach sterownika JDBC (lub Hibernate, nie jestem pewien) domyślnym zachowaniem było "bezpośrednie przechowywanie". Później zmieniono to na "pośrednie przechowywanie". Prawdopodobnie z jakiegoś dobrego powodu. –

+0

Zastanowiłem się trochę nad tym i zacząłem się coraz bardziej zgadzać z a_horse_with_no_name. Po pierwsze, pośredni mechanizm magazynowania uniemożliwia korzystanie z tej kolumny w zapytaniu HQL, co jest wielką wadą. Pośredni mechanizm przechowywania ułatwia opcję przesyłania strumieniowego, dzięki czemu można przesyłać strumieniowo zawartość bezpośrednio z bazy danych do klienta (oszczędzając zużycie pamięci). Na pewno jest to poprawny argument dla obiektów BLOB, ale dla obiektów CLOB? W większości scenariuszy rozmiar rzeczywistych CLOB nie będzie tak duży, z pewnością nie w zakresie 1M lub więcej. Można to zrobić w pamięci. –

Odpowiedz

5

Po wielu grzebać w kodzie źródłowym Hibernate i kierowcy PostgreSQL JDBC udało mi się znaleźć przyczynę problemu. Na koniec metoda write() BlobOutputStream (dostarczana przez sterownik JDBC) jest wywoływana, aby zapisać zawartość Clob do bazy danych. Sposób ten wygląda następująco:

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()); 
    } 
} 

metoda ta jest „int” (32 bitów/4 bajty) jako argumentu, i przekształca je do „bajt” (8 bitów/1 bajt) skutecznie utraty 3 bajty informacji . Reprezentacje napisów w Javie są kodowane w UTF-16, co oznacza, że ​​każdy znak jest reprezentowany przez 16 bitów/2 bajty. Znak euro ma wartość int 8364. Po konwersji na bajt wartość 172 pozostaje (w reprezentacji oktetowej 254).

Nie jestem pewien, jaka jest teraz najlepsza rozdzielczość tego problemu. IMHO sterownik JDBC powinien być odpowiedzialny za kodowanie/dekodowanie znaków Java UTF-16 w celu zakodowania potrzeb bazy danych. Jednak nie widzę możliwości korekty w kodzie sterownika JDBC, aby zmienić jego zachowanie (i nie chcę pisać i utrzymywać własnego kodu sterownika JDBC).

Dlatego też rozszerzyłem Hibernate z niestandardowym ClobType i udało się przekonwertować znaki UTF-16 na UTF-8 przed zapisaniem do bazy danych i na odwrót podczas pobierania Clob.

Rozwiązania są zbyt duże, aby po prostu łatwo wkleić tę odpowiedź. Jeśli jesteś zainteresowany, napisz do mnie, a ja to ci wyślę.

Cheers, Franck

+0

Franck Mam dookoła tego po prostu używając niesamowitego dużego varchar (który jest kolumna tekstu postgresowego). Wiem, że nie jest idealny, ponieważ kolumna varchar jest prawdopodobnie ładowana do pamięci (zamiast tego, że bufor jest prawdopodobnie buforowany na dysk, gdy jest duży), ale działa. –

+0

Franck, zachowanie 'BlobOutputStream.write (int b)' jest poprawne. Cokolwiek to będzie wywoływać, prawdopodobnie będzie go niewłaściwie używać. Zgodnie z [OutputStream JavaDoc] (http://docs.oracle.com/javase/6/docs/api/java/io/OutputStream.html#write (int)) * "Ogólny kontrakt na' write' jest taki, że jeden bajt jest zapisywany w strumieniu wyjściowym. Bajt do zapisania to osiem bitów niskiego rzędu argumentu b. 24 bity wyższego rzędu b są ignorowane. "* Czy masz przypadek testowy, który demonstruje ten problem? Jeśli tak, zgłoś błąd Hibernate i link do niego tutaj. (Pomagam w sterowniku JDBC) –

+0

Jeśli sterownik PostgreSQL zaimplementował już obsługę dla NClob, może mógłbyś spróbować użyć hibernacji zamiast Nobla zamiast Clob? To był mój plan na znacjonalizowaną obsługę znaków w Hibernate w każdym razie: wspieranie znacjonalizowanych wariantów zdefiniowanych w JDBC 4 (Types.NCLOB, Types.NCHAR, Types.NVARCHAR, Types.NLONGVARCHAR) –

Powiązane problemy