Using a InputStream with BlobProxy and Envers results in java.sql.SQLException: could not reset reader

Description

With `import org.hibernate.engine.jdbc.BlobProxy` it is possible to use an `InputStream` to store binary data in an entity. But if such an entity is marked as audited, and the `Blob` field is audited, this results in an exception (only final parts of the stack trace):

```
Caused by: org.hibernate.HibernateException: Unable to access blob stream
at org.hibernate.type.descriptor.java.BlobTypeDescriptor.unwrap(BlobTypeDescriptor.java:138)
at org.hibernate.type.descriptor.java.BlobTypeDescriptor.unwrap(BlobTypeDescriptor.java:33)
at org.hibernate.type.descriptor.sql.BlobTypeDescriptor$5$1.doBind(BlobTypeDescriptor.java:151)
at org.hibernate.type.descriptor.sql.BlobTypeDescriptor$2$1.doBind(BlobTypeDescriptor.java:86)
at org.hibernate.type.descriptor.sql.BasicBinder.bind(BasicBinder.java:73)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:276)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:271)
at org.hibernate.type.AbstractSingleColumnStandardBasicType.nullSafeSet(AbstractSingleColumnStandardBasicType.java:39)
at org.hibernate.persister.entity.AbstractEntityPersister.dehydrate(AbstractEntityPersister.java:3071)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3370)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3906)
at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:107)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604)
at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478)
at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:684)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:344)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1391)
... 46 more
Caused by: java.sql.SQLException: could not reset reader
at org.hibernate.engine.jdbc.BlobProxy.resetIfNeeded(BlobProxy.java:74)
at org.hibernate.engine.jdbc.BlobProxy.getUnderlyingStream(BlobProxy.java:63)
at org.hibernate.type.descriptor.java.BlobTypeDescriptor.unwrap(BlobTypeDescriptor.java:113)
... 65 more
```

I created a test case here: https://github.com/jpdigital/hibernate-testcase-blob-envers-inputstream/tree/main/orm/hibernate-orm-5

Activity

Show:

Grim Oire March 1, 2024 at 12:50 PM
Edited

Folks,

I stream a http-post-request-body-stream directly into the genuine-entity.

int contentLength = request.getContentLength(); LobCreator creator = Hibernate.getLobCreator((SessionImplementor) entityManager.getDelegate()); Blob blobValue = creator.createBlob(servletBodyStream, contentLength); entity.setMyBinaryData(blobValue); entityManager.persist(entity);

Since the genuine-entity is under Enver-Audit control, the Enver-Audit-Plugin try to save a second entity (and a second blob accordingly), the _AUD-entity.

Since the browser naivly closes the stream a second blob for the _AUD-Entity is not available anymore.

I think this is still major and should be fixed. But I understand that this might never happen because it is very complex material.

Alex February 20, 2024 at 7:35 AM
Edited

A possible workaround that worked for me (Spring Boot Application with Hibernate 6+) is to refresh the entity with the blob using the EntityManager. This seems to reopen a closed underlying InputStream.

Another workaround is to use a Session and get the Entity with the Blob from the Session and then operate on the InputStream / bytes[].

Chris Cranford December 19, 2021 at 9:50 AM

In fact, using the raw byte[] even without any changes to BlobProxy will work as expected. It’s going to be problematic only when passing the InputStream which is not recommended again due to the inconsistent behaviors across dialects.

Chris Cranford December 19, 2021 at 9:43 AM

So rather than passing an InputStream if you pass the raw byte[] the underlying stream doesn’t get closed prematurely and allows the BlobProxy to work with PostgreSQL. Going to adjust the test case to use this and see whether that works across all dialects.

Chris Cranford December 19, 2021 at 9:27 AM

So an unfortunate problem with the PostgreSQL driver is that it automatically closes the underlying stream associated with the BlobProxy once the value has been read by the driver. This means that when Envers goes to read the value during the insertion to the audit table; a NullPointerException will be thrown since the stream has been closed. It would also appear that Sybase drivers behave in the same way.

So even if we were to adjust the behavior of BlobProxy to automatically create the mark position so that the stream could be reset without external help; the behavior would still not be portable across all dialects sadly.

Fixed

Details

Assignee

Reporter

Fix versions

Affects versions

Priority

Created July 12, 2021 at 6:00 PM
Updated February 13, 2025 at 10:17 AM
Resolved December 17, 2024 at 12:11 PM