How to release blobs with hibernate and spring @tr

2019-05-12 00:00发布

I have a project with Spring and Hibernate. In my implementation, there are multiple models where I have to read from a file and store it as a Blob. For this purpose, I have an abstract class defined with Blob as follows. All the model that need to store blobs extend this class.

@MappedSuperclass
public abstract class AbstractContentAwareBean {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    protected Long id;

    public AbstractContentAwareBean() {}

    @Lob
    @Column(name = "content", columnDefinition = "LONGBLOB")
    protected Blob content;

    public void setContent(Blob content) {
        this.content = content;
    }

    public void setContent(Session currentSession, InputStream inputStream, long size) {
        Blob blob = Hibernate.getLobCreator(currentSession).createBlob(inputStream, size);
        setContent(blob);
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @JsonIgnore
    public Blob getContent() {
        return content;
    }
}

Here is a sample usage of this.

@Transactional
public void sampleMethod() throws Exception {
    try {
        // I have entity manager auto-wired. 
        Session session = entityManager.unwrap(Session.class);

        File file = new File("/tmp/filepath.txt");
        MySampleModel sm = new MySampleModel();
        sm.setContent(session, new FileInputStream(file), file.length());
        // I have service auto wired. persistSampleModel simply calls mySampleRepository.save(sm)
        mySampleModelService.persistSampleModel(sm);

    } catch (Exception e) {
        // log and throw
        throw new Exception("Error occurred", e);
    }
}

Now the problem I face is that the FileInputStream I open here is not released. I checked with lsof -p <process_id> command and those file descriptors remain.

I checked the implementation path of Blob creation and the stream goes through org.hibernate.engine.jdbc.BlobProxy and ends up in org.hibernate.engine.jdbc.BlobProxy.StreamBackedBinaryStream which has a release method but it is not called automatically. Therefore I believe we have to call the Blob.free() method manually to get the release called.

Attempt 1: try-with-resources

I get the following trace when I wrap the stream with try-with-resources. I believe the issue here is that the stream is read only when the transaction commits and by then try-with-resources has closed the stream.

Caused by: java.io.IOException: Stream Closed
    at java.io.FileInputStream.readBytes(Native Method) ~[?:1.8.0_151]
    at java.io.FileInputStream.read(FileInputStream.java:255) ~[?:1.8.0_151]
    at com.mysql.cj.jdbc.PreparedStatement.readblock(PreparedStatement.java:2612) ~[mysql-connector-java-6.0.5.jar:6.0.5]
    at com.mysql.cj.jdbc.PreparedStatement.streamToBytes(PreparedStatement.java:4302) ~[mysql-connector-java-6.0.5.jar:6.0.5]
    at com.mysql.cj.jdbc.PreparedStatement.fillSendPacket(PreparedStatement.java:2144) ~[mysql-connector-java-6.0.5.jar:6.0.5]
    at com.mysql.cj.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2013) ~[mysql-connector-java-6.0.5.jar:6.0.5]
    at com.mysql.cj.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:1970) ~[mysql-connector-java-6.0.5.jar:6.0.5]
    at com.mysql.cj.jdbc.PreparedStatement.executeLargeUpdate(PreparedStatement.java:4984) ~[mysql-connector-java-6.0.5.jar:6.0.5]
    at com.mysql.cj.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1955) ~[mysql-connector-java-6.0.5.jar:6.0.5]
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:205) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.dialect.identity.GetGeneratedKeysDelegate.executeAndExtract(GetGeneratedKeysDelegate.java:57) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:42) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2909) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3480) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:81) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:626) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:280) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:261) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:306) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:318) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:275) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:113) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:804) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:771) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.jpa.event.internal.core.JpaPersistEventListener$1.cascade(JpaPersistEventListener.java:80) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:414) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:252) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:113) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:58) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:780) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:765) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_151]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_151]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_151]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_151]
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298) ~[spring-orm-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at com.sun.proxy.$Proxy76.persist(Unknown Source) ~[?:?]
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:508) ~[spring-data-jpa-1.11.6.RELEASE.jar:?]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_151]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_151]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_151]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_151]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:504) ~[spring-data-commons-1.13.6.RELEASE.jar:?]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:489) ~[spring-data-commons-1.13.6.RELEASE.jar:?]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:461) ~[spring-data-commons-1.13.6.RELEASE.jar:?]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:56) ~[spring-data-commons-1.13.6.RELEASE.jar:?]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) ~[spring-tx-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282) ~[spring-tx-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) ~[spring-tx-4.3.10.RELEASE.jar:4.3.10.RELEASE]
    ... 48 more

Attempt 2: javax.persistence.PostPersist annotated method to call Blob.free() method

@PostPersist
public void postPersist(){
    try {
        this.content.free();
    } catch (SQLException ignore) {}
}

This was successful in most of the cases than the try with resources attempt. Yet in CascadeType.MERGE cases where one model uses another, this attempt also fails.

Caused by: org.hibernate.exception.GenericJDBCException: unable to merge BLOB data
        at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:47) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.dialect.Dialect$3.mergeBlob(Dialect.java:597) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.type.BlobType.getReplacement(BlobType.java:39) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.type.BlobType.getReplacement(BlobType.java:20) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.type.AbstractStandardBasicType.replace(AbstractStandardBasicType.java:353) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.type.TypeHelper.replace(TypeHelper.java:180) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.event.internal.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:394) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:203) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:176) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:903) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:873) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:261) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:461) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:202) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:176) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:69) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:881) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:867) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_151]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_151]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_151]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_151]
        at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:347) ~[spring-orm-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        at com.sun.proxy.$Proxy160.merge(Unknown Source) ~[?:?]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_151]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_151]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_151]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_151]
        at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298) ~[spring-orm-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        at com.sun.proxy.$Proxy160.merge(Unknown Source) ~[?:?]
        at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:511) ~[spring-data-jpa-1.11.5.RELEASE.jar:?]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_151]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_151]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_151]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_151]
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:504) ~[spring-data-commons-1.13.5.RELEASE.jar:?]
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:489) ~[spring-data-commons-1.13.5.RELEASE.jar:?]
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:461) ~[spring-data-commons-1.13.5.RELEASE.jar:?]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:56) ~[spring-data-commons-1.13.5.RELEASE.jar:?]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) ~[spring-tx-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282) ~[spring-tx-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) ~[spring-tx-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        ... 137 more
Caused by: javax.sql.rowset.serial.SerialException: Error: You cannot call a method on a SerialBlob instance once free() has been called.
        at javax.sql.rowset.serial.SerialBlob.isValid(SerialBlob.java:592) ~[?:1.8.0_151]
        at javax.sql.rowset.serial.SerialBlob.getBinaryStream(SerialBlob.java:220) ~[?:1.8.0_151]
        at org.hibernate.dialect.Dialect$3.mergeBlob(Dialect.java:594) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.type.BlobType.getReplacement(BlobType.java:39) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.type.BlobType.getReplacement(BlobType.java:20) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.type.AbstractStandardBasicType.replace(AbstractStandardBasicType.java:353) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.type.TypeHelper.replace(TypeHelper.java:180) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.event.internal.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:394) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:203) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:176) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:903) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:873) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:261) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:461) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:202) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:176) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:69) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:881) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:867) ~[hibernate-core-5.2.10.Final.jar:5.2.10.Final]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_151]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_151]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_151]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_151]
        at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:347) ~[spring-orm-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        at com.sun.proxy.$Proxy160.merge(Unknown Source) ~[?:?]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_151]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_151]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_151]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_151]
        at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298) ~[spring-orm-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        at com.sun.proxy.$Proxy160.merge(Unknown Source) ~[?:?]
        at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:511) ~[spring-data-jpa-1.11.5.RELEASE.jar:?]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_151]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_151]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_151]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_151]
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:504) ~[spring-data-commons-1.13.5.RELEASE.jar:?]
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:489) ~[spring-data-commons-1.13.5.RELEASE.jar:?]
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:461) ~[spring-data-commons-1.13.5.RELEASE.jar:?]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:56) ~[spring-data-commons-1.13.5.RELEASE.jar:?]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) ~[spring-tx-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282) ~[spring-tx-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) ~[spring-tx-4.3.10.RELEASE.jar:4.3.10.RELEASE]
        ... 137 more

Questions:

  1. Does hibernate or spring generally release the Blobs automatically? If so could it be a configuration problem?

  2. If hibernate or spring does not release blob automatically, how can we release the Blob in such scenarios?

Let me share my confugurations as well. Hope some of you could help me. Thanks in advance.

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
      p:packagesToScan="org.sample.model" p:dataSource-ref="dataSource">

    <property name="jpaProperties">
        <props>
            <prop key="hibernate.hbm2ddl.auto">${spring.jpa.hibernate.ddl-auto}</prop>
            <prop key="hibernate.implicit_naming_strategy">${spring.jpa.hibernate.naming.implicit-strategy}</prop>
            <prop key="hibernate.physical_naming_strategy">${spring.jpa.hibernate.naming.physical-strategy}</prop>
        </props>
    </property>

    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="generateDdl" value="false"/>
            <property name="showSql" value="${spring.jpa.show-sql}"/>
            <property name="databasePlatform" value="${spring.jpa.database-platform}"/>
        </bean>
    </property>
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>

<jpa:repositories base-package="org.sample.repository"/>

<context:component-scan base-package="org.sample.repository"/>

P.S: The usage of FileInputStream in the above sample method is to simplify the issue. Actually, in the code, I used an UltraESB-X API to read a stream from a temporary file created by the ESB.

1条回答
祖国的老花朵
2楼-- · 2019-05-12 01:04

Since there were no answers for this, I'll post mine. The solution we could come up with was to write a class which wraps an input stream and closes it automatically once the reading is complete.

/**
 * Purpose of this class is to wrap an input stream and close it automatically once the reading is complete.
 *
 * @author Rajind Ruparathna
 */
public class SelfClosingInputStream extends FilterInputStream {

    private static final int EOF = -1;

    private boolean isClosed = false;
    private Long length;
    private Long count = 0L;

    public SelfClosingInputStream(InputStream is, Long length) {
        super(is);
        this.length = length;
    }

    public SelfClosingInputStream(InputStream is) {
        super(is);
        length = -1L;
    }

    @Override
    public int read() throws IOException {
        try {
            if (!isClosed)  {
                int val = super.read();
                count = count + 1;
                if ((length != -1L && count.equals(length)) || val == EOF) {
                    close();
                    isClosed = true;
                }
                return val;
            } else {
                return EOF;
            }
        } catch (IOException e) {
            close();
            isClosed = true;
            throw e;
        }
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        try {
            if (!isClosed)  {
                int val = super.read(b, off, len);
                count = count + val;
                if ((length != -1L && count.equals(length)) || val == EOF) {
                    close();
                    isClosed = true;
                }
                return val;
            } else {
                return EOF;
            }
        } catch (IOException e) {
            close();
            isClosed = true;
            throw e;
        }
    }
}

This class was used in the AbstractContentAwareBean class as follows.

public void setContent(Session currentSession, InputStream inputStream, long size) {
    Blob blob = Hibernate.getLobCreator(currentSession).createBlob(new SelfClosingInputStream(inputStream, size), size);
    setContent(blob);
}

Would like to thank the folks including the people who answered my question at code review for helping me write the SelfClosingInputStream class.

查看更多
登录 后发表回答