Hibernate Blobs und Input Streams
Nach einigen Tests mit der Blob Unterstützung in Hibernate musste ich leider feststellen, dass sich Hibernate nicht dazu eignet Blobs aus einem InputStream einzulesen, wenn die Gesamtgrösse der einzulesenden Daten nicht bekannt ist und man die Daten nicht alle im Speicher halten will.
Auf das Problem wurde ich aufmerksam als ich feststellte, dass Blobs aus einigen InputStreams nicht korrekt eingelesen wurden. Zum Einsatz kam Hibernate 3.3.2, ein Blick in die Quellen offenbart einen Fehler in dieser Version:
public static Blob createBlob(InputStream stream) throws IOException {
return new SerializableBlob( new BlobImpl( stream, stream.available() ) );
}
Hier wird eine Methode angesprochen, die die Länge der einzulesenden Daten erwartet. Fälschlicherweise wird hier .available() des Streams verwendet. Dieser Wert gibt allerdings nur die Anzahl an Bytes zurück, die garantiert gelesen werden können, bevor der Stream das nächste Mal blockiert. Der Wert hat also keine Aussagekraft bzgl. der Gesamtgrösse der Daten des Streams.
http://java.sun.com/javase/6/docs/api/java/io/InputStream.html#available%28%29
Der Fehler ist bekannt:
In den aktuellen Versionen von Hibernate wurde dieser Fehler behoben, allerdings wird jetzt der ganze Stream in einen Puffer gezogen, damit Hibernate die Gesamtgröße kennt:
public static Blob createBlob(InputStream stream) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream( stream.available() );
StreamUtils.copy( stream, buffer );
return createBlob( buffer.toByteArray() );
}
So etwas ist natürlich völlig inakzeptabel, wenn man sauber mit größeren Datenmengen arbeiten will.
Ich werde nun auf die native PostgreSQL Blob API umsteigen, das nimmt mir zwar den Hibernate Komfort, ermöglicht aber mehr Kontrolle über den Speicherverbrauch.
http://www.postgresql.org/files/documentation/books/pghandbuch/html/jdbc-binary-data.html
Eine weitere Alternative wäre es, Daten aus einem InputStream zuerst in eine temporäre Datei zu schrieben, und sie erst anschließend in die Datenbank einzulesen, da dann ja die Gesamtgröße bekannt ist.

