0

I have problem when trying to save large files in an Oracle 11g database, I am using Spring and Hibernate 3.6.9 as JPA implementation.

The persistence uses Spring and is configured as follow:

<bean id="entityManagerFactory"
            class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="databasePlatform" value="org.hibernate.dialect.Oracle10gDialect" />
            <property name="generateDdl" value="false" />
        </bean>
    </property>
    <property name="jpaDialect">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
    </property>
    <property name="loadTimeWeaver">
        <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </property>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
            <prop key="hibernate.connection.SetBigStringTryClob">true</prop>
            <prop key="hibernate.jdbc.batch_size">0</prop>
            <prop key="hibernate.jdbc.use_streams_for_binary">true</prop>
        </props>
    </property>
</bean>

The entity I want to persist contains a column defined like:

@Column(name = "FILE_OBJECT")
@Lob
private byte[] fileObject;
...

I encouter this stackTrace:

java.lang.OutOfMemoryError: Java heap space
at java.lang.reflect.Array.newArray(Native Method)
at java.lang.reflect.Array.newInstance(Array.java:52)
at org.hibernate.type.descriptor.java.ArrayMutabilityPlan.deepCopyNotNull(ArrayMutabilityPlan.java:44)
at org.hibernate.type.descriptor.java.MutableMutabilityPlan.deepCopy(MutableMutabilityPlan.java:58)
at org.hibernate.type.AbstractStandardBasicType.deepCopy(AbstractStandardBasicType.java:314)
at org.hibernate.type.AbstractStandardBasicType.deepCopy(AbstractStandardBasicType.java:310)
at org.hibernate.type.TypeHelper.deepCopy(TypeHelper.java:68)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:302)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:203)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:143)
at org.hibernate.ejb.event.EJB3MergeEventListener.saveWithGeneratedId(EJB3MergeEventListener.java:62)
at org.hibernate.event.def.DefaultMergeEventListener.saveTransientEntity(DefaultMergeEventListener.java:415)
at org.hibernate.event.def.DefaultMergeEventListener.mergeTransientEntity(DefaultMergeEventListener.java:341)
at org.hibernate.event.def.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:303)
at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:258)
at org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:877)
at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:859)
at org.hibernate.engine.CascadingAction$6.cascade(CascadingAction.java:279)
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:392)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:335)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
at org.hibernate.engine.Cascade.cascade(Cascade.java:161)
at org.hibernate.event.def.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:450)
at org.hibernate.event.def.DefaultMergeEventListener.mergeTransientEntity(DefaultMergeEventListener.java:336)
at org.hibernate.event.def.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:303)
at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:258)
at org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:877)
at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:859)
at org.hibernate.engine.CascadingAction$6.cascade(CascadingAction.java:279)
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:392)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:335)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)

I googled a lot but I found nothing helpful, I still get the same error, if some one can help? Some links mentioned to use another Oracle driver but as I can see in the stacktrace, the issue is in the Hibernate code, I don't think that the problem is related to the DB (and I use the latest driver ojdbc6).

893
  • 185
  • 1
  • 5
  • 14
  • Have you tried adding more memory to the JVM? You can't load arbitarily large files in memory and expect it will always work. If the JVM doesn't have enough memory to hold the file, it will throw an OutOfMemoryError. – JB Nizet Nov 01 '13 at 12:01
  • Did you try the solution in the first answer here: http://stackoverflow.com/questions/9253323/how-to-persist-large-blobs-100mb-in-oracle-using-hibernate – David Levesque Nov 01 '13 at 12:24
  • @jb Nizet: Currently, I allocated 2gb, it should be enough, I guess? – 893 Nov 01 '13 at 12:49
  • @David Levesque: Which one? I tested the code with the TypeDescriptor but it contains some errors that I cannot solve (some classes are missing: BinaryStreamWrapper, Integrator) – 893 Nov 01 '13 at 12:52
  • It all depends on the size of the file. If the file 3GB-large, 2GB is not enough. – JB Nizet Nov 01 '13 at 12:53
  • The one talking about custom UserType: http://stackoverflow.com/a/9313997/1464763 – David Levesque Nov 01 '13 at 13:57
  • It's about Hibernate 4 and I use Hibernate 3.6 :-/ – 893 Nov 01 '13 at 16:42

1 Answers1

0

Finally, I think that I found a solution, defining a custom type linked to the byte array. Some explanations:

First, I created the CustomMaterializedBlobType, Hibernate uses it to make the link between an array of byte and a Blob, here, its main role is to provide a MutabilityPlan (customized) that does not a real copy of array (it was my problem):

import org.hibernate.type.AlternativeLobTypes.BlobTypes;
import org.hibernate.type.MaterializedBlobType;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;

public class CustomMaterializedBlobType extends MaterializedBlobType{



/**
 * 
 */
    private static final long serialVersionUID = 1L;

    public CustomMaterializedBlobType() {
        super();
    }

    public CustomMaterializedBlobType(SqlTypeDescriptor sqlTypeDescriptor, BlobTypes<byte[], MaterializedBlobType> blobTypes) {
        super(sqlTypeDescriptor, blobTypes);
    }

    @SuppressWarnings("unchecked")
    @Override
    protected MutabilityPlan<byte[]> getMutabilityPlan() {

        return CustomArrayMutabilityPlan.INSTANCE;
    }
}

Now, the MutabilityPlan, does nothing else than returning the same array instead creating a new one: import org.hibernate.type.descriptor.java.ArrayMutabilityPlan;

public class CustomArrayMutabilityPlan<T> extends ArrayMutabilityPlan<T>{

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    public static final CustomArrayMutabilityPlan INSTANCE = new CustomArrayMutabilityPlan();

    @SuppressWarnings({ "unchecked", "SuspiciousSystemArraycopy" })
    public T deepCopyNotNull(T value) {
        if ( ! value.getClass().isArray() ) {
            // ugh!  cannot find a way to properly define the type signature here to
            throw new IllegalArgumentException( "Value was not an array [" + value.getClass().getName() + "]" );
        }

        return value;
    }
}

And I need to link this custimized type to the Lob, it's done in my entity:

@Lob
@Type(type = "CustomMaterializedBlobType")
@Column(name = "FILE_OBJECT")
private byte[] fileObject;

It's not a very nice solution, because this way, I made my application dependant of Hibernate, instead of JPA (the type annotation comes from Hibernate). If some one knows how I can do the same in full JPA?

893
  • 185
  • 1
  • 5
  • 14