Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Ленивая и жадная загрузка

Загружая статью, Hibernate должен решить, грузить ли сразу её автора и комментарии. Жадная (eager) загрузка приносит их в том же запросе или сразу после. Ленивая (lazy) откладывает: связанные данные читаются только тогда, когда код к ним обратится. Выбор задаётся параметром fetch аннотации связи.

Модель блога дальше

С этого урока примеры используют полную модель: Author, Post, Comment и Tag. Связи 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 тоже ссылается на статью через @ManyToOne(fetch = FetchType.LAZY). Тестовые данные создаёт вспомогательный класс:

// 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("Анна");
            Author oleg = new Author("Олег");
            Author maria = new Author("Мария");
            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("Привет, Hibernate", anna);
            p1.setViews(120);
            p1.getTags().add(java);
            p1.getTags().add(hibernate);
            p1.addComment(new Comment("Отличная статья!"));
            p1.addComment(new Comment("Спасибо, Анна"));
 
            Post p2 = new Post("Типы record в Java 25", oleg);
            p2.setViews(80);
            p2.getTags().add(java);
            p2.addComment(new Comment("Очень понятно"));
 
            Post p3 = new Post("Ленивая загрузка", maria);
            p3.setViews(45);
            p3.getTags().add(hibernate);
 
            em.persist(p1);
            em.persist(p2);
            em.persist(p3);
        });
    }
}

Следим за запросами

// src/main/java/com/example/blog/Main.java
Seed.run(emf);
emf.runInTransaction(em -> {
    Post post = em.find(Post.class, 1L);
    IO.println("Заголовок: " + post.getTitle());
    IO.println("Класс автора: " + post.getAuthor().getClass().getSimpleName());
    IO.println("Автор: " + post.getAuthor().getName());
    IO.println("Комментариев: " + 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=?
Заголовок: Привет, Hibernate
Класс автора: Author$HibernateProxy
Hibernate: select a1_0.id,a1_0.name from Author a1_0 where a1_0.id=?
Автор: Анна
Hibernate: select c1_0.post_id,c1_0.id,c1_0.text from Comment c1_0 where c1_0.post_id=?
Комментариев: 2

find загрузил только статью. Вместо автора Hibernate положил в поле прокси — подкласс-заместитель, который знает только id. Настоящий запрос выполнился при первом getName(). Комментарии загрузились при первом size().

С @ManyToOne без fetch = FetchType.LAZY автор приходит сразу, через 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=?

Значения по умолчанию

СвязьПо умолчанию
@ManyToOne, @OneToOneEAGER
@OneToMany, @ManyToManyLAZY

Обычный совет — делать все связи ленивыми, то есть добавлять fetch = FetchType.LAZY к @ManyToOne и @OneToOne, а связанные данные загружать явно там, где они нужны. Как это делать — тема следующего урока.

Частая ошибка

Ленивую связь можно загрузить, только пока EntityManager открыт. За пределами транзакции будет ошибка:

// src/main/java/com/example/blog/Main.java
Post post = emf.callInTransaction(em -> em.find(Post.class, 1L));
IO.println("Автор загружен: " + Hibernate.isInitialized(post.getAuthor()));
IO.println("Автор: " + post.getAuthor().getName());
Автор загружен: false
Exception in thread "main" org.hibernate.LazyInitializationException: Could not initialize proxy [com.example.blog.Author#1] - no session

Hibernate.isInitialized проверяет, загружены ли данные. Загружайте всё нужное внутри транзакции — через join fetch или граф сущности из следующего урока.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.