#HOW TO: Lazy Loading of JPA attributes with Hibernate

Modeling your domain model with JPA is quite easy and for a smaller amount of data, you can easily rely on the default configuration of JPA/Hibernate. But when it comes to performance issues in your application you need a deeper understanding of JPA/Hibernate. One big performance boost could be Lazy Loading. When it comes to Lazy Loading, most of the developer think of using this optimization for entity relationships like @OneToMany/@OneToOne/@ManyToMany, which is commonly used to disable the loading of the whole entity graph within fetching one entity. Another, not widely used, optimization is the Lazy Loading of attributes for a JPA entity, which I’ll demonstrate in this blog post.

Let’s assume you are writing a web application and you want to store the file uploads of your users to your database (notwithstanding the fact this is a good idea or not). A possible JPA entity for this task could look like the following:

@Data // Lombok annotation to generate constructor/getter/setter...
@Entity
public class FileUpload {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String fileName;

    private String fileType;

    @Lob
    private byte[] fileContent;

    @CreationTimestamp // Hibernate annotation
    private LocalDateTime uploadedAt;
}

One possible use case might be to inspect all uploaded files from a user in Java code. Therefore you don’t want to load all the byte array representation of the files in your memory at first. To lazy load the fileContent you have to modify the attribute and add the following @Basic(fetch = FetchType.LAZY) annotation:

@Data // Lombok annotation to generate constructor/getter/setter...
@Entity
public class FileUpload {

    // ... same as above 

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

}

With just this annotation, Hibernate wouldn’t be able to lazy load the file content per default. To get this running you have to enhance the bytecode of your JPA entity, for which Hibernate offers a Maven plugin:

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

After running mvn clean compile or mvn clean package/install the FileUpload class file contains some more fields and methods with the prefix $$_hibernate_ like the following:

public byte[] $$_hibernate_read_fileContent() { 
  /* compiled code */ 
}

public void $$_hibernate_write_fileContent(byte[] bytes) { 
  /* compiled code */ 
}

With this enhancement, Hibernate is now able to lazy load the access to the fileContent. To demonstrate this, I’ll use a small Spring Boot app where I store the application.properties  file in an in-memory database (H2) on startup and query for it in another class. For visualization of the lazy loading, I enabled the logging of the SQL statements:

@Component
@Order(1)
@Slf4j
public class StoreFileOnStartup implements CommandLineRunner {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    @Transactional
    public void run(String... args) throws Exception {

        File file = new File(getClass().getClassLoader().getResource("application.properties").getFile());
        byte[] fileBytes = new byte[(int) file.length()];
        FileInputStream fis = new FileInputStream(file);
        fis.read(fileBytes);

        FileUpload fileUpload = new FileUpload();
        fileUpload.setFileName(file.getName());
        fileUpload.setFileType("Property file");
        fileUpload.setFileContent(fileBytes);

        entityManager.persist(fileUpload);
        entityManager.flush();

        log.info("--- File successfully stored to the database");

    }
}

@Component
@Order(2)
@Slf4j
public class ReadFileOnStartup implements CommandLineRunner {

    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    @Override
    public void run(String... args) throws Exception {

        log.info("--- Loading file from database");

        FileUpload allFileUploads = entityManager.find(FileUpload.class, 1L);

        log.info("--- File successfully loaded from database");

        log.info("--- Accessing fileContent");

        byte[] content = allFileUploads.getFileContent();

        log.info("--- the file has: " + content.length + " bytes");

    }
}

When running the Spring Boot application the following log is produced:

Hibernate: insert into file_upload (id, file_content, file_name, file_type, uploaded_at) values (null, ?, ?, ?, ?)
INFO 12992 --- [main] d.r.b.l.StoreFileOnStartup: --- File successfully stored to the database
INFO 12992 --- [main] d.r.b.l.ReadFileOnStartup: --- Loading file from database
Hibernate: select fileupload0_.id as id1_0_0_, fileupload0_.file_name as file_nam3_0_0_, fileupload0_.file_type as file_typ4_0_0_, fileupload0_.uploaded_at as uploaded5_0_0_ from file_upload fileupload0_ where fileupload0_.id=?
INFO 12992 --- [main] d.r.b.l.ReadFileOnStartup: --- File successfully loaded from database
INFO 12992 --- [main] d.r.b.l.ReadFileOnStartup: --- Accessing fileContent
Hibernate: select fileupload_.file_content as file_con2_0_ from file_upload fileupload_ where fileupload_.id=?
INFO 12992 --- [main] d.r.b.l.ReadFileOnStartup: --- the file has: 55 bytes

You can see that Hibernate created three SQL queries, one for the insert, another for the select and the third for accessing the fileContent. You can find the whole example on GitHub.

PS: This was one of the many great tips/hints/insights about JPA/Hibernate I got from the excellent book High-Performance Java Persistence from @vlad_mihalcea.

Happy Lazy Loading,

Phil

Leave a comment

Your email address will not be published. Required fields are marked *