← Назад к вопросам

Как работает FetchType.LAZY?

2.0 Middle🔥 201 комментариев
#ORM и Hibernate

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

FetchType.LAZY в Hibernate/JPA

FetchType.LAZY — это стратегия загрузки связанных данных в Hibernate, при которой связанные сущности загружаются из базы данных только при первом обращении к ним, а не сразу при загрузке основной сущности.

Основная концепция

When you load an entity with LAZY fetch type, Hibernate:

  1. Загружает основную сущность
  2. Создает прокси-объект для связанных сущностей (заполнитель)
  3. Загружает связанные данные только когда вы обратитесь к ним

Пример с One-to-Many отношением

@Entity
public class Author {
    @Id
    private Long id;
    private String name;
    
    @OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
    private List<Book> books; // Загружается лениво
}

@Entity
public class Book {
    @Id
    private Long id;
    private String title;
    
    @ManyToOne(fetch = FetchType.LAZY)
    private Author author; // Загружается лениво
}

Как это работает на практике

// Это загружает ТОЛЬКО автора
Author author = entityManager.find(Author.class, 1L);
System.out.println(author.getName()); // "John Doe"

// SQL запрос: SELECT * FROM author WHERE id = 1
// Список книг еще НЕ загружен!

// Первое обращение к books — выполняется SQL запрос
for (Book book : author.getBooks()) {
    System.out.println(book.getTitle());
}
// SQL запрос: SELECT * FROM book WHERE author_id = 1

LazyInitializationException — частая ошибка

@Transactional
public void processAuthor(Long id) {
    Author author = entityManager.find(Author.class, id);
    return author; // Транзакция закрывается
}

// В другом методе
Author author = processAuthor(1L);
for (Book book : author.getBooks()) {
    // LazyInitializationException! 
    // Sesssion уже закрыта, книги загрузить невозможно
    System.out.println(book.getTitle());
}

Решение 1: Инициализировать в транзакции

@Transactional
public Author getAuthorWithBooks(Long id) {
    Author author = entityManager.find(Author.class, id);
    // Инициализировать коллекцию до выхода из транзакции
    Hibernate.initialize(author.getBooks());
    return author;
}

Решение 2: Использовать FETCH JOIN в JPQL

@Transactional(readOnly = true)
public Author getAuthorWithBooks(Long id) {
    return entityManager.createQuery(
        "SELECT DISTINCT a FROM Author a LEFT JOIN FETCH a.books WHERE a.id = :id",
        Author.class
    )
    .setParameter("id", id)
    .getSingleResult();
    // books уже загружены в одном запросе
}

Решение 3: Открытая сессия в представлении (OpenSessionInView)

// Spring Boot: application.properties
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

LAZY vs EAGER

LAZY (рекомендуется):

  • Загружает только необходимые данные
  • Быстрее при работе с основной сущностью
  • Может привести к N+1 query проблемам
  • Требует открытой сессии/транзакции

EAGER:

  • Загружает сразу все связанные данные
  • Может привести к лишним запросам
  • Безопаснее в многослойном приложении
  • Часто приводит к загрузке ненужных данных
@Entity
public class Author {
    @OneToMany(fetch = FetchType.EAGER) // Опасно!
    private List<Book> books; // Загружается при КАЖДОЙ загрузке автора
}

N+1 Query проблема с LAZY

List<Author> authors = entityManager.createQuery(
    "SELECT a FROM Author a",
    Author.class
).getResultList(); // 1 запрос: SELECT * FROM author

for (Author author : authors) {
    System.out.println(author.getBooks().size());
    // N запросов: SELECT * FROM book WHERE author_id = ?
    // Итого: 1 + N запросов!
}

Решение: Batch Loading

@Entity
public class Author {
    @OneToMany(fetch = FetchType.LAZY)
    @BatchSize(size = 10)
    private List<Book> books; // Загружается батчами по 10
}

// Теперь при загрузке 10 авторов будет:
// 1 запрос на авторов
// 1 запрос на книги (с IN clause на 10 авторов)
// Всего 2 запроса вместо 11!

Проверка что объект инициализирован

Author author = entityManager.find(Author.class, 1L);

// Проверить инициализирован ли
if (Hibernate.isInitialized(author.getBooks())) {
    System.out.println("Книги загружены");
} else {
    System.out.println("Книги не загружены (прокси)");
}

// Force инициализацию
Hibernate.initialize(author.getBooks());

Best Practices

  1. По умолчанию используй LAZY для всех отношений
  2. Явно загружай нужные данные через FETCH JOIN или @EntityGraph
  3. Избегай LazyInitializationException — инициализируй в транзакции
  4. Батчируй запросы если загружаешь много объектов
  5. Логируй SQL (hibernate.show_sql=true) чтобы видеть проблемы
  6. Используй @EntityGraph для специфичных случаев
@NamedEntityGraph(name = "author-with-books",
    attributeNodes = @NamedAttributeNode("books")
)
@Entity
public class Author { ... }

// Использование
Author author = entityManager.find(Author.class, 1L, 
    Map.of("javax.persistence.fetchgraph", 
           entityManager.getEntityGraph("author-with-books")));

Итоговый вывод

FetchType.LAZY — мощный инструмент для оптимизации производительности, но требует понимания как работают Hibernate сессии и транзакции. Главное правило: загружай данные там где они тебе нужны, а не там где они определены.