Wie zum streamen von Daten in die Datenbank BLOB mit Hibernate (keine in-memory-Speicherung in byte[])
Ich bin auf der Suche nach einem Weg, um Strom von binären Daten zu/von der Datenbank. Wenn möglich, möchte ich die Arbeit mit Hibernate (Datenbank-unabhängige Art und Weise).
Alle Lösungen die ich gefunden habe, beinhalten explizite oder implizite laden von binären Daten in den Speicher als byte[]. Ich brauche zu vermeiden. Sagen wir, ich will mein code zum schreiben in eine lokale Datei eine 2-GB-video aus einer Datenbank (gespeichert in der BLOB-Spalte), oder die andere Weise herum, mit nicht mehr als 256Mb Speicher. Es ist klar erreichbar und beinhaltet kein voodoo. Aber ich kann nicht einen Weg finden, für die ich jetzt versuche zu vermeiden, debugging Hibernate.
Betrachten wir Beispiel-code (halten im Verstand -Jmx=256Mb).
Entity-Klasse:
public class SimpleBean {
private Long id;
private Blob data;
//... skipping getters, setters and constructors.
}
Hibernate mapping fragment:
<class name="SimpleBean" table="SIMPLE_BEANS">
<id name="id" column="SIMPLE_BEAN_ID">
<generator class="increment" />
</id>
<property name="data" type="blob" column="DATA" />
</class>
Test-code-fragment:
Configuration cfg = new Configuration().configure("hibernate.cfg.xml");
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
.applySettings(cfg.getProperties())
.buildServiceRegistry();
SessionFactory sessionFactory = cfg.buildSessionFactory(serviceRegistry);
Session session = sessionFactory.openSession();
session.beginTransaction();
File dataFile = new File("movie_1gb.avi");
long dataSize = dataFile.length();
InputStream dataStream = new FileInputStream(dataFile);
LobHelper lobHelper = session.getLobHelper();
Blob dataBlob = lobHelper.createBlob(dataStream, dataSize);
session.save( new SimpleBean(data) );
session.getTransaction().commit(); //Throws java.lang.OutOfMemoryError
session.close();
blobStream.close();
sessionFactory.close();
Beim laufen das snippet bekomme ich OutOfMemory-exception. Auf der Suche im stack-trace zeigt, was mit Hibernate zu laden versucht den stream in den Speicher und bekommt OutOfMemory (wie es sollte). Hier die stack-trace:
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2271)
at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:113)
at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93)
at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:140)
at org.hibernate.type.descriptor.java.DataHelper.extractBytes(DataHelper.java:183)
at org.hibernate.type.descriptor.java.BlobTypeDescriptor.unwrap(BlobTypeDescriptor.java:121)
at org.hibernate.type.descriptor.java.BlobTypeDescriptor.unwrap(BlobTypeDescriptor.java:45)
at org.hibernate.type.descriptor.sql.BlobTypeDescriptor$4$1.doBind(BlobTypeDescriptor.java:105)
at org.hibernate.type.descriptor.sql.BasicBinder.bind(BasicBinder.java:92)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:305)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:300)
at org.hibernate.type.AbstractSingleColumnStandardBasicType.nullSafeSet(AbstractSingleColumnStandardBasicType.java:57)
at org.hibernate.persister.entity.AbstractEntityPersister.dehydrate(AbstractEntityPersister.java:2603)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2857)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3301)
at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:88)
at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:362)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:354)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:275)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:326)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:52)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1214)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:403)
at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:175)
at ru.swemel.msgcenter.domain.SimpleBeanTest.testBasicUsage(SimpleBeanTest.java:63)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
Verwendet Hibernate 4.1.5.SP1. Die genaue Frage ist: wie vermeiden Sie das laden-stream in den Speicher, wenn das speichern eines blob in der Datenbank mit Hilfe von Hibernate, mit direct-streaming statt. Ich möchte vermeiden, aus Themen darüber, warum man speichert video in der Spalte der Datenbank, anstatt es zu speichern in einigen content-repository und verknüpfen. Bitte, halten Sie es für ein Modell, was für die Fragestellung irrelevant.
Es scheint, dass es möglicherweise irgendeine Art von Fähigkeiten auf verschiedene Dialekte und Überwintern könnten versuchen, zu laden, alles im Speicher, weil die zugrunde liegende Datenbank nicht unterstützt streaming-blobs oder so ähnlich. Wenn es der Fall ist - ich würde gerne sehen, eine Art vergleichende Tabelle zwischen verschiedenen Dialekte im Aspekt der Umgang mit blobs.
Danken Ihnen sehr für Ihre Hilfe!
Du musst angemeldet sein, um einen Kommentar abzugeben.
Für diejenigen, die für die gleiche Sache.
Mir schlecht, der code funktioniert wie soll (z.B. streams, ohne zu versuchen, zu kopieren, zu memory) for PostgreSQL (und wahrscheinlich viele andere). Die innere Arbeit, Hibernate, hängt von ausgewählten Dialekt. Die, die ich verwendet, in den ersten Platz überschreibt die direkte Nutzung der streams zu Gunsten von BinaryStream unterstützt von byte[].
Es gibt auch keine Probleme mit Leistung, da lädt es nur OID (Anzahl) im Fall von PostgreSQL, und wahrscheinlich zu faul, lädt die Daten, die in anderen Dialekten (einschließlich byte [] - Implementierung). Lief nur ein paar schmutzige tests keinen sichtbaren Unterschied in 10000 Lasten von Unternehmen mit und ohne binäre Daten-Feld.
Speicherung von Daten in der Datenbank scheint langsamer zu sein als nur das speichern auf der Festplatte als externe Datei obwohl. Aber es speichert Sie eine Menge Kopfschmerzen, wenn Sie ein Backup, oder der Umgang mit Einschränkungen für bestimmte Datei-system, oder gleichzeitige Aktualisierungen, etc. Aber es ist ein off-topic.
Ihre Lösung mit Hibernate ist lobHelper sollte funktionieren, aber Sie müssen sicherstellen, dass die Verwendung von streams durchgesetzt.
Set property hibernate.jdbc.use_streams_for_binary = true
Dies ist ein system-level-Eigenschaft, so dass es gesetzt werden muss, um beim Start (ich definierte es auf der Kommandozeile während der Prüfung:
Können Sie belegen, dass es geändert im code:
Sie speichern die
Blob
in Ihrem POJOSimpleBean
. Dies bedeutet, wenn der blob größer als der heap-Speicher, die Sie jederzeit bei der Arbeit mit diesem Objekt oder Zugriff auf diedata
Feld, du gehst, um dieOutOfMemoryError
weil die ganze Sache in den Speicher geladen wird.Ich glaube nicht, dass es eine Möglichkeit zum festlegen oder bekommen eine Datenbank Feld mit einem Stream in hibernate und HQL-Einsätze nur in SELECT-Anweisungen.
Was Sie möglicherweise tun müssen, ist entfernen Sie die
data
Feld aus derSimpleBean
Objekt so, dass es nicht im Speicher gespeichert, wenn Sie laden oder speichern. Aber wenn Sie einen blob verwenden, können Sie den Ruhezustand istsave()
zu erstellen, die Zeile, dann mit einem jdbc-PreparedStatement und diesetBinaryStream()
Methode. Wenn Sie brauchen, um Zugriff auf den stream, die Sie verwenden können, hibernate istload()
Methode, um einenSimpleBean
Objekt aus und führen Sie einen jdbc-wählen Sie um eineResultSet
dann verwenden Sie diegetBinaryStream()
- Methode zum Lesen der blob. Die docs fürsetBinaryStream()
sagen:Damit die Daten nicht gespeichert werden, vollständig im Arbeitsspeicher.