Spring, Hibernate, Blob lazy loading
Asked Answered
E

8

32

I need help with lazy blob loading in Hibernate. I have in my web application these servers and frameworks: MySQL, Tomcat, Spring and Hibernate.

The part of database config.

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <property name="driverClass" value="${jdbc.driverClassName}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>

    <property name="initialPoolSize">
        <value>${jdbc.initialPoolSize}</value>
    </property>
    <property name="minPoolSize">
        <value>${jdbc.minPoolSize}</value>
    </property>
    <property name="maxPoolSize">
        <value>${jdbc.maxPoolSize}</value>
    </property>
    <property name="acquireRetryAttempts">
        <value>${jdbc.acquireRetryAttempts}</value>
    </property>
    <property name="acquireIncrement">
        <value>${jdbc.acquireIncrement}</value>
    </property>
    <property name="idleConnectionTestPeriod">
        <value>${jdbc.idleConnectionTestPeriod}</value>
    </property>
    <property name="maxIdleTime">
        <value>${jdbc.maxIdleTime}</value>
    </property>
    <property name="maxConnectionAge">
        <value>${jdbc.maxConnectionAge}</value>
    </property>
    <property name="preferredTestQuery">
        <value>${jdbc.preferredTestQuery}</value>
    </property>
    <property name="testConnectionOnCheckin">
        <value>${jdbc.testConnectionOnCheckin}</value>
    </property>
</bean>


<bean id="lobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler" />

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="/WEB-INF/hibernate.cfg.xml" />
    <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">${hibernate.dialect}</prop>
        </props>
    </property>
    <property name="lobHandler" ref="lobHandler" />
</bean>

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

<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

The part of entity class

@Lob
@Basic(fetch=FetchType.LAZY)
@Column(name = "BlobField", columnDefinition = "LONGBLOB")
@Type(type = "org.springframework.orm.hibernate3.support.BlobByteArrayType")
private byte[] blobField;

The problem description. I'm trying to display on a web page database records related to files, which was saved in MySQL database. All works fine if a volume of data is small. But the volume of data is big I'm recieving an error java.lang.OutOfMemoryError: Java heap space I've tried to write in blobFields null values on each row of table. In this case, application works fine, memory doesn't go out of. I have a conclusion that the blob field which is marked as lazy (@Basic(fetch=FetchType.LAZY)) isn't lazy, actually!

Eyecatching answered 9/4, 2010 at 6:26 Comment(1)
See also hibernate.onjira.com/browse/HHH-5255Naominaor
L
40

I'm confused. Emmanuel Bernard wrote in ANN-418 that @Lob are lazy by default (i.e. you don't even need to use the @Basic(fetch = FetchType.LAZY) annotation).

Some users report that lazy loading of a @Lob doesn't work with all drivers/database.

Some users report that it works when using bytecode instrumentation (javassit? cglib?).

But I can't find any clear reference of all this in the documentation.

At the end, the recommended workaround is to use a "fake" one-to-one mappings instead of properties. Remove the LOB fields from your existing class, create new classes referring to the same table, same primary key, and only the necessary LOB fields as properties. Specify the mappings as one-to-one, fetch="select", lazy="true". So long as your parent object is still in your session, you should get exactly what you want. (just transpose this to annotations).

Lumpish answered 10/4, 2010 at 23:8 Comment(8)
Thank you, Pascal. I didn't understand how can i specify the mapping one-to-one whith parent class. Would you mind helping me with it? Thank you again.Eyecatching
Parent class @OneToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY) private FileBlobBean fileBlobBean; Child class: @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Basic(optional = false) @Column(name = "Uid", nullable = false) private Long uid; @Lob @Basic(fetch = FetchType.LAZY) @Column(name = "BlobField", columnDefinition = "LONGBLOB") private byte[] blobField; @OneToOne(mappedBy = "fileBlobBean") @JoinColumn(name = "Uid", referencedColumnName = "Uid", nullable = false) public FileBean fileBean;Eyecatching
Hibernate created a new field in the table "files". And the field was empty. What am i doing wrong? P.S. I'm sorry for awful code formatting.Eyecatching
In my case this helped: Parent: @OneToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY) @PrimaryKeyJoinColumn private FileBlobBean... Child: just id, no reference to parentPharaoh
Can you show how to implement the OneToOne relationship of blob?Jose
How to annotate the child (binary) entity? I tried but it auto-increment one more row that contains only binary data!Jose
Bytecode instrumentation example reference using Maven can be find hereHuertas
What if I assign entity IDs from a sequence? In this case during save of parent entity, child gets created in a new row with new ID and all fields empty excluding field which is holding blob content.Accidie
H
7

Lazy property loading requires buildtime bytecode instrumentation.

Hibernate docs: Using lazy property fetching

If you want to avoid bytecode instrumentation one option is to to create two entities that use same table, one with the blob one without. Then only use the entity with blob when you need the blob.

Howardhowarth answered 9/8, 2016 at 4:1 Comment(0)
W
5

I would suggest you to use inheritance to handle this scenario. Have a base class without the blob and a derived class containing the byte array. You would use the derived class only when you need to display the blob on the UI.

Waers answered 9/4, 2010 at 6:32 Comment(1)
Thank you, Darin. But i want to know why the lazy field isn't so lazy as i want :) But, of cource, I'll use you suggestion, if I don't find the another way to solve the problem.Eyecatching
C
5

Of course you could extract that value and put it into a new table with a "@OneToOne" relation that is lazy, however in our application the LOBs are loaded lazily on demand using just this configuration

@Lob
@Fetch(FetchMode.SELECT)
@Type(type="org.hibernate.type.PrimitiveByteArrayBlobType")
byte[] myBlob;

This is tested in our project simultaneously on PostgreSQL, MySQL, SQLServer and Oracle, so it should work for u

Christcrossrow answered 4/1, 2012 at 14:18 Comment(2)
Didn't work for me either. Hibernate 4.3.1. Oracle 10g XE. Do you perhaps have byte code instrumentation turned implemented ?Howardhowarth
PrimitiveByteArrayBlobType is Deprecated,repalced with MaterializedBlobType, and didn't work for me too(hibernate 5.0.7.Final,postgresql)Veolaver
S
4

I had the same issue and this was my fix:

My Entity:

@Entity
@Table(name = "file")
public class FileEntity {

@Id
@GeneratedValue
private UUID id;

@NotNull
private String filename;

@NotNull
@Lob @Basic(fetch = FetchType.LAZY)
private byte[] content;

...

Added plugin to pom.xml:

        <plugin>
            <groupId>org.hibernate.orm.tooling</groupId>
            <artifactId>hibernate-enhance-maven-plugin</artifactId>
            <executions>
                <execution>
                    <phase>compile</phase>
                    <configuration>
                        <failOnError>true</failOnError>
                        <enableLazyInitialization>true</enableLazyInitialization>
                    </configuration>
                    <goals>
                        <goal>enhance</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
Simonson answered 20/6, 2018 at 12:49 Comment(1)
What does pom.xml entry do specifically?Marenmarena
B
3

Lazy loading works for me if I use Blob type instead of byte[].

@Column(name = "BlobField", nullable = false)
@Lob
@Basic(fetch = FetchType.LAZY)
private Blob blobField;

This one gets lazily loaded and if you need to retrieve its value access this field:

String value = IOUtils.toByteArray(entity.getBlobField().getBinaryStream());
Bitthia answered 5/10, 2021 at 10:8 Comment(1)
My reading of the docs (ie, for Hibernate 5.5) is that FetchType.LAZY is merely a hint but not a guarantee that the instance attribute will be lazy-loaded. If you want the guarantee, you need to configure in the bytecode enhancements at build-time.May
B
2

For me lazy load only worked by compiling and then running it, didn't work on eclipse or intellij for example.

I'm using gradle then I did the following to get it working

  • Annotate entity
  • Setup Hibernate gradle plugin

build.gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.hibernate:hibernate-gradle-plugin:5.4.0.Final"
    }
}

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'org.hibernate.orm'
hibernate {
    enhance {
        enableLazyInitialization = true
        enableDirtyTracking = true
        enableAssociationManagement = true
    }
}

Entity.java

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Integer id;

    @Lob
    @Basic(fetch = FetchType.LAZY)
    @Column(length = 255, nullable = false)
    private String name;

Testing

./gradlew run

Full working example

Bankable answered 10/1, 2019 at 20:58 Comment(0)
S
0

A simple workarround using @OneTone notation based on the response of @MohammadReza Alagheband (Why does @Basic(fetch=lazy) doesn't work in my case?) but without the requirement of create a new table for each required lazy attribute is the following:

@Getter
@Setter
@Entity
@Table(name = "document")
@AllArgsConstructor
@NoArgsConstructor
public class DocumentBody implements java.io.Serializable{
    @Column(name = "id", insertable = false)
    @ReadOnlyProperty
    @Id
    @PrimaryKeyJoinColumn
    private Integer id;

    @Column(name = "body", unique = true, nullable = false, length = 254)
    @JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
    private String content;
}

@Getter
@Entity
@Setter
@Table(name = "document")
@AllArgsConstructor
@NoArgsConstructor
public class DocumentTitle implements java.io.Serializable{
    @Column(name = "id", insertable = false)
    @ReadOnlyProperty
    @Id
    private Integer id;

    @Column(name = "title", unique = true, nullable = false, length = 254)
    @JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
    private String content;
}


public class Document implements java.io.Serializable {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    @JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
    private Integer id;

    //Also it is posssible to prove with @ManyToOne
    @OneToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinColumn(name = "id", referencedColumnName = "id", nullable = false)
    @JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
    private DocumentTitle title;

    @OneToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinColumn(name = "id", referencedColumnName = "id", nullable = false)
    @JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
    private DocumentBody body;
}
Slider answered 21/12, 2021 at 20:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.