Lazy and eager loading
When you load a post, Hibernate has to decide whether to load its author and comments right away. Eager loading brings them in the same query or immediately after. Lazy loading postpones it: related data is fetched only when the code touches it. The choice is made with the fetch parameter of a relationship annotation.
The blog model from now on
From this lesson on, the examples use the full model: Author, Post, Comment and Tag. The relationships of Post:
// src/main/java/com/example/blog/Post.java
private String title;
private int views;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
private Author author;
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Comment> comments = new ArrayList<>();
@ManyToMany
@JoinTable(name = "post_tag",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id"))
private Set<Tag> tags = new HashSet<>();Comment also refers to its post with @ManyToOne(fetch = FetchType.LAZY). Test data is created by a helper class:
// src/main/java/com/example/blog/Seed.java
package com.example.blog;
import jakarta.persistence.EntityManagerFactory;
public class Seed {
public static void run(EntityManagerFactory emf) {
emf.runInTransaction(em -> {
Author anna = new Author("Anna");
Author oleg = new Author("Oleg");
Author maria = new Author("Maria");
em.persist(anna);
em.persist(oleg);
em.persist(maria);
Tag java = new Tag("java");
Tag hibernate = new Tag("hibernate");
em.persist(java);
em.persist(hibernate);
Post p1 = new Post("Hello, Hibernate", anna);
p1.setViews(120);
p1.getTags().add(java);
p1.getTags().add(hibernate);
p1.addComment(new Comment("Great post!"));
p1.addComment(new Comment("Thanks, Anna"));
Post p2 = new Post("Records in Java 25", oleg);
p2.setViews(80);
p2.getTags().add(java);
p2.addComment(new Comment("Very clear"));
Post p3 = new Post("Lazy loading", maria);
p3.setViews(45);
p3.getTags().add(hibernate);
em.persist(p1);
em.persist(p2);
em.persist(p3);
});
}
}Watching the queries
// src/main/java/com/example/blog/Main.java
Seed.run(emf);
emf.runInTransaction(em -> {
Post post = em.find(Post.class, 1L);
IO.println("Title: " + post.getTitle());
IO.println("Author class: " + post.getAuthor().getClass().getSimpleName());
IO.println("Author: " + post.getAuthor().getName());
IO.println("Comments: " + post.getComments().size());
});Hibernate: select p1_0.id,p1_0.author_id,p1_0.title,p1_0.views from Post p1_0 where p1_0.id=?
Title: Hello, Hibernate
Author class: Author$HibernateProxy
Hibernate: select a1_0.id,a1_0.name from Author a1_0 where a1_0.id=?
Author: Anna
Hibernate: select c1_0.post_id,c1_0.id,c1_0.text from Comment c1_0 where c1_0.post_id=?
Comments: 2find loaded only the post. Instead of the author, Hibernate put a proxy into the field — a stand-in subclass that knows only the id. The real query ran at the first getName(). The comments were loaded at the first size().
With @ManyToOne without fetch = FetchType.LAZY the author arrives at once, through a join:
Hibernate: select p1_0.id,a1_0.id,a1_0.name,p1_0.title,p1_0.views from Post p1_0 left join Author a1_0 on a1_0.id=p1_0.author_id where p1_0.id=?Defaults
| Relationship | Default |
|---|---|
@ManyToOne, @OneToOne | EAGER |
@OneToMany, @ManyToMany | LAZY |
The usual advice is to make every relationship lazy — that is, to add fetch = FetchType.LAZY to @ManyToOne and @OneToOne — and to load related data explicitly where it is needed. How to do that is the topic of the next lesson.
Common mistake
A lazy relationship can be loaded only while the EntityManager is open. Outside the transaction you get an error:
// src/main/java/com/example/blog/Main.java
Post post = emf.callInTransaction(em -> em.find(Post.class, 1L));
IO.println("Author loaded: " + Hibernate.isInitialized(post.getAuthor()));
IO.println("Author: " + post.getAuthor().getName());Author loaded: false
Exception in thread "main" org.hibernate.LazyInitializationException: Could not initialize proxy [com.example.blog.Author#1] - no sessionHibernate.isInitialized checks whether the data has been loaded. Load everything you will need inside the transaction — with join fetch or an entity graph from the next lesson.