Photo by Jisun Han on Unsplash

Lazy loading images from database

Milan Brankovic
3 min readJul 29, 2021

When it comes to storing content (images, documents, videos,…) inside the database we all know of BLOB (Binary Large Object), CLOB (Character Large Object), VARBINARY, etc. data types. They are used for storing large amount of data. They basically represent a binary string of variable length, stored as a sequence of bytes or octets.

By using some of the ORM frameworks (like Hibernate) the property of the entity class related to the mapping of file content would look like this:

@Lob
@Column(name = "file_content", columnDefinition = "LONGBLOB")
private byte[] content;

This is perfectly fine, and it will work for small files, but it comes with performance cost. If file content is large and you are querying for multiple files which satisfy some conditions, you can easily get the timeout on the client side, or OutOfMemoryException, or some other exception; as holding large volume of data in Hibernate comes with a cost.

The solution would be to lazy load the content, which will fetch the content only when it's actually needed. Hibernate supports the lazy fetching of individual properties.

There are several different techniques to mitigate this problem and depending on the database vendor some of them will work and some will not.

Solution #1

@Lob annotation works as lazy loading out-of-the-box and you don't need to do anything.

Solution #1.1

If the solution with only @Lob annotation does not work out-of-the-box, a little help from javaassits is necessary. Add the dependency

<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>${javassist.version}</version>
</dependency>

Solution #2

For some database vendors @Lob annotation does not work as it should. This depends on the Hibernate version (≥ 3.5) used in the project. The solution in this case should be to use @Type(type = "org.hibernate.type.MaterializedBlobType")

Solution #2.1

If some solutions mentioned above do not work, with some additional specific annotations, like the ones below, the solutions would work

@Lob
@Column(name = "file_content", columnDefinition = "LONGBLOB")
@Fetch(FetchMode.SELECT)
@Type(type="org.hibernate.type.MaterializedBlobType")
private byte[] content;

Solution #3

By using @Basic(fetch=FetchType.LAZY) on the property itself. This solution requires activation of Hibernate BytecodeEnhancement

<plugin>
<groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<version>${hibernate.version}</version>
<executions>
<execution>
<configuration>
<enableLazyInitialization>true</enableLazyInitialization>
</configuration>
<goals>
<goal>enhance</goal>
</goals>
</execution>
</executions>
</plugin>

The goal of the Hibernate Enhance plugin is to instrument the JPA entity bytecode in order to improve the effectiveness and efficiency of the associated data access operations. By setting the enableLazyInitialization to true, Hibernate changes the bytecode of the getter and setter methods to intercept the call and trigger the property initialization on demand.

Solution #4

Instead of doing equilibristics with hibernate annotations, one may just try converting the field from String/byte[] into Clob (or Blob):

@Lob  
@Basic(fetch=FetchType.LAZY)
@Column(name = "file_content", columnDefinition = "LONGBLOB")
public Blob getFieldBlob() {
return content;
}
public void setFieldBlob(final Blob blobContent) {
this.content = blobContent;
}
@Transient
public String getField() {
if (getFieldBlob() == null){
return null;
}

try {
return MyOwnUtils.readStream(getFieldBlob().getCharacterStream());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public void setField(final String field)
{
this.fieldBlob = Hibernate.createBlob(field);
}

Solution #5

This solution is the way to go and works for all db vendors. The core of the implementation lies in mapping of multiple sub-entities to the same database table.

@MappedSuperclass
public class BaseAttachment {

@Id
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid2")
@Column(name = "id")
private String id;

@NotBlank
@Column(name = "name", nullable = false)
private String name;

@NotBlank
@Column(name = "file_type", nullable = false)
private String fileType;
}
@Entity
@Table(name = "attachment")
public class AttachmentSummary extends BaseAttachment {
}
@Entity
@Table(name = "attachment")
public class Attachment extends BaseAttachment {

@Lob
@Column(name = "data", nullable = false, columnDefinition = "LONGBLOB")
private byte[] data;
}

Solution #5.1

The similar solution to this is extracting file content to one-to-one relationship which will be lazy loaded.

--

--