2012-04-03 11 views
10

Ich habe Probleme beim Schreiben und Lesen Sonderzeichen wie das Euro-Zeichen (€) in LOB String-Eigenschaften in PostgreSQL 8.4 mit Hibernate 3.6.10.Kann nicht Euro-Zeichen in LOB String Eigenschaft mit Hibernate/PostgreSQL speichern

Ich weiß, dass PostgreSQL zwei verschiedene Möglichkeiten bietet, große Zeichenobjekte in einer Spalte einer Tabelle zu speichern. Sie können entweder direkt in dieser Tabellenspalte oder indirekt in einer separaten Tabelle gespeichert werden (sie heißt eigentlich pg_largeobject). Im letzteren Fall enthält die Spalte eine Referenz (OID) für die Zeile in pg_largeobject.

Das Standardverhalten in Hibernate 3.6.10 ist der indirekte OID-Ansatz. Es ist jedoch möglich, der Eigenschaft Lob eine zusätzliche Anmerkung @ org.hibernate.annotations.Type (type = "org.hibernate.type.TextType") hinzuzufügen, um das Verhalten des direkten Speichers zu erhalten.

Beide Ansätze funktionieren gut, außer für den Moment, dass ich mit Sonderzeichen wie dem Eurozeichen (€) arbeiten möchte. In diesem Fall funktioniert der direkte Speichermechanismus weiter, aber der indirekte Speichermechanismus bricht.

Ich möchte das anhand eines Beispiels demonstrieren. Ich habe eine Testeinheit mit 2 @Lob-Eigenschaften erstellt. Man folgt den direkten Speicherprinzip, das andere die indirekte Lagerung:

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

und

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

Wenn ich ein Unternehmen zu erstellen, füllen beide Eigenschaften mit dem Zeichen Euro und es dann bestehen bleiben auf der Datenbank Ich sehe die folgenden, wenn ich eine SELECT sehe ich

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

Wenn ich dann die Tabelle pg_largeobject Abfrage ich sehe:

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

Die 'data'-Spalte von pg_largeobject ist vom Typ bytea, was bedeutet, dass die Information als rohe Bytes gespeichert wird. Der Ausdruck '\ 254' steht für ein einzelnes Byte und in UTF-8 für das Zeichen '¬'. Dies ist genau der Wert, den ich zurückbekomme, wenn ich die Entity von der Datenbank zurücklade.

Die Euro-Zeichen in UTF-8 bestehen aus 3 Bytes, so würde ich die Spalte ‚Daten‘ zu erwarten habe 3 Bytes zu haben und nicht 1.

Dies ist nur für die Euro-Zeichen nicht auftritt, sondern für viele Sonderzeichen. Ist das ein Problem in Hibernate? Oder der JDBC-Treiber? Gibt es eine Möglichkeit, dieses Verhalten zu ändern?

Vielen Dank im Voraus,
Mit freundlichen Grüßen
Franck de Bruijn

+1

Warum sind Sie Große Objekte in erster Linie mit? Verwenden Sie einfach den Datentyp 'text' für diese Spalte. Sie müssen sich nicht mit "bytea" oder großen Objekten herumärgern, wenn Sie nur Text speichern möchten. –

+0

Es könnte viele Gründe geben, dies zu tun. Ich weiß es nicht. Ich biete einen Rahmen für andere Benutzer an und möchte beide Alternativen unterstützen. In älteren Versionen des JDBC-Treibers (oder Hibernate, ich bin mir nicht sicher) war das Standardverhalten "direkter Speicher". Später wurde dies in "indirekte Speicherung" geändert. Wahrscheinlich aus einem guten Grund. –

+0

Ich dachte ein wenig darüber nach und ich stimme mehr und mehr mit a_horse_with_no_name überein. Zunächst einmal verhindert der indirekte Speichermechanismus, dass Sie diese Spalte in einer HQL-Abfrage verwenden, was ein großer Nachteil ist. Der indirekte Speichermechanismus erleichtert die Streaming-Option, sodass Sie den Inhalt direkt von der Datenbank zum Client streamen können (spart Arbeitsspeicher). Sicher ist dies ein gültiges Argument für BLOBs, aber für CLOBs? In den meisten Szenarien wird die Größe der tatsächlichen CLOBs nicht so groß sein, definitiv nicht im Bereich von 1M oder höher. Dies kann im Speicher gehandhabt werden. –

Antwort

5

Nach viel um im Quellcode von Hibernate und den PostgreSQL-JDBC-Treiber graben konnte ich die Ursache des Problems finden. Am Ende wird die write() - Methode des BlobOutputStream (vom JDBC-Treiber bereitgestellt) aufgerufen, um den Inhalt des Clobs in die Datenbank zu schreiben. Dieses Verfahren sieht wie folgt aus:

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

Diese Methode verwendet eine 'int' (32 Bits/4 Bytes) als Argument auf und wandelt es zu einem 'Byte' (8 Bits/1 Byte) effektiv zu verlieren 3 Bytes von Informationen . String-Repräsentationen in Java sind UTF-16-kodiert, was bedeutet, dass jedes Zeichen durch 16 Bits/2 Bytes repräsentiert wird. Das Euro-Zeichen hat den int-Wert 8364. Nach der Konvertierung in Byte bleibt der Wert 172 (in Oktettdarstellung 254).

Ich bin mir nicht sicher, was jetzt die beste Lösung für dieses Problem ist. IMHO sollte der JDBC-Treiber für das Kodieren/Dekodieren der Java UTF-16-Zeichen zu jeder Kodierung, die die Datenbank benötigt, verantwortlich sein. Allerdings sehe ich im JDBC-Treibercode keine Optimierungsmöglichkeiten, um sein Verhalten zu ändern (und ich möchte meinen eigenen JDBC-Treibercode nicht schreiben und pflegen).

Daher habe ich Hibernate mit einem benutzerdefinierten ClobType erweitert und es geschafft, die UTF-16-Zeichen in UTF-8 vor dem Schreiben in die Datenbank und umgekehrt beim Abrufen der Clob konvertieren.

Die Lösung ist zu groß, um diese Antwort einfach einzufügen. Wenn Sie interessiert sind, schreiben Sie mir eine Nachricht und ich schicke es Ihnen.

Cheers, Franck

+0

Franck Ich habe um diese mit nur unglaubliche große varchar (was ist postgres Textspalte). Ich weiß, dass es nicht ideal ist, da die Varchar-Spalte wahrscheinlich in den Speicher geladen wird (anstatt dass der Clob wahrscheinlich auf die Festplatte gepuffert wird, wenn er groß ist), aber es funktioniert. –

+0

Franck, das Verhalten von 'BlobOutputStream.write (int b)' ist korrekt. Was immer es anruft, wird es wahrscheinlich falsch benutzen. Gemäß dem [Output JavaDoc] (http://docs.oracle.com/javase/6/docs/api/java/io/OutputStream.html#write (int)) * "Der Rahmenvertrag für' write' ist, dass ein Byte in den Ausgabestream geschrieben. Das Byte geschrieben werden soll die acht Bits niedriger Ordnung des Arguments b. die 24 höherwertigen Bits von b ignoriert werden. "* haben Sie einen Testfall haben, der dieses Problem demonstriert? Wenn ja, dann bitte einen Fehler gegen Hibernate und verlinke hier. (I helfen, sich auf den JDBC-Treiber aus) –

+0

Wenn der PostgreSQL-Treiber-Unterstützung noch für NCLOB umgesetzt hat vielleicht könnten Sie versuchen, Hibernate NCLOB statt Clob verwenden zu müssen? Das war mein Plan für die verstaatlichte Zeichenunterstützung in dem Ruhezustand überhaupt: die verstaatlichten Varianten in JDBC 4 (Types.NCLOB, Types.NCHAR, Types.NVARCHAR, Types.NLONGVARCHAR) definierten Unterstützung –