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

Что такое жадная загрузка?

2.0 Middle🔥 151 комментариев
#SOLID и паттерны проектирования#Spring Boot и Spring Data

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

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

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

Жадная загрузка (Eager Loading) в Java/ORM

Жадная загрузка (Eager Loading) — это стратегия загрузки связанных данных, при которой все связанные объекты загружаются сразу же вместе с основным объектом, в одном или нескольких SQL запросах. Противоположность ленивой загрузке (Lazy Loading).

Концепция

// Пример: Автор и его Книги
Author author = authorRepository.findById(1);

// При жадной загрузке:
// 1. Загружается сам Author
// 2. СРАЗУ загружаются все его Books

author.getBooks();  // данные уже в памяти, нет доп. SQL запроса

Жадная загрузка в Hibernate/JPA

1. Аннотация @Eager на сущности:

@Entity
public class Author {
    @Id
    private Long id;
    
    private String name;
    
    @OneToMany(mappedBy = "author", fetch = FetchType.EAGER)
    private List<Book> books;  // будет загружена всегда
    
    // геттеры/сеттеры
}

@Entity
public class Book {
    @Id
    private Long id;
    
    private String title;
    
    @ManyToOne(fetch = FetchType.EAGER)
    private Author author;  // будет загружена всегда
}

2. Explicit Join Fetch в запросе:

// Используя JPQL
public interface AuthorRepository extends JpaRepository<Author, Long> {
    @Query("SELECT a FROM Author a JOIN FETCH a.books WHERE a.id = :id")
    Author findByIdWithBooks(@Param("id") Long id);
}

// Использование
Author author = authorRepository.findByIdWithBooks(1L);
author.getBooks();  // уже загружено

3. QueryDSL пример:

public Author findWithBooks(Long id) {
    return queryFactory
        .selectFrom(author)
        .leftJoin(author.books).fetchJoin()
        .where(author.id.eq(id))
        .fetchOne();
}

Жадная загрузка в Spring Data

public interface AuthorRepository extends JpaRepository<Author, Long> {
    // Загрузить автора со всеми связанными данными
    @EntityGraph(attributePaths = "books")
    Optional<Author> findById(Long id);
    
    // Или
    @EntityGraph(attributePaths = {"books", "awards", "publisher"})
    List<Author> findAll();
}

Жадная загрузка в MyBatis

@Select("""
    SELECT 
        a.id, a.name,
        b.id as book_id, b.title
    FROM authors a
    LEFT JOIN books b ON a.id = b.author_id
    WHERE a.id = #{id}
""")
@Results({
    @Result(property = "id", column = "id"),
    @Result(property = "name", column = "name"),
    @Result(property = "books", column = "id",
        many = @Many(select = "getBooks"))
})
Author findWithBooks(Long id);

Практический пример: E-commerce

@Entity
public class Order {
    @Id
    private Long id;
    
    private String orderNumber;
    
    @ManyToOne(fetch = FetchType.EAGER)
    private Customer customer;  // жадная загрузка
    
    @OneToMany(mappedBy = "order", fetch = FetchType.EAGER)
    private List<OrderItem> items;  // жадная загрузка
    
    @ManyToOne(fetch = FetchType.EAGER)
    private Warehouse warehouse;  // жадная загрузка
}

@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    // Явно указываем, какие отношения загружать
    @EntityGraph(attributePaths = {"customer", "items", "warehouse"})
    Optional<Order> findById(Long id);
}

// Использование
Order order = orderRepository.findById(1L).orElse(null);
if (order != null) {
    System.out.println(order.getCustomer().getName());  // нет доп. запроса
    order.getItems().forEach(item -> System.out.println(item.getProduct()));
}

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

Жадная загрузка (EAGER):

Производится запрос:
SELECT a.*, b.* FROM authors a LEFT JOIN books b ON a.id = b.author_id

Результат:
- Один (или мало) запросов
- Все данные в памяти сразу
- Может загрузить МНОГО ненужных данных

Ленивая загрузка (LAZY):

Производится запрос:
SELECT * FROM authors WHERE id = 1

Затем при доступе к books:
SELECT * FROM books WHERE author_id = 1

Результат:
- Много запросов (N+1 problem)
- Данные загружаются по требованию
- Экономит память

Проблема N+1

// Плохо: ленивая загрузка создает N+1 запрос
List<Author> authors = authorRepository.findAll();  // Запрос 1
for (Author author : authors) {  // Каждый вызов book.getAuthor() = новый запрос
    System.out.println(author.getName());
    author.getBooks().forEach(book -> System.out.println(book.getTitle()));
}
// Итого: 1 + N запросов!

// Хорошо: жадная загрузка решает N+1
@EntityGraph(attributePaths = "books")
List<Author> findAll();  // Один запрос с JOIN

Когда использовать жадную загрузку

  • Связанные данные всегда нужны (например, Order → Customer)
  • Выбирается одна сущность или небольшое количество
  • Нужна максимальная производительность на чтение
  • Хотим избежать N+1 problem

Когда избегать жадной загрузки

  • Выбирается большой список сущностей
  • Связанные данные не всегда нужны
  • Может возникнуть декартово произведение (multiple @OneToMany)
  • Нужна максимальная производительность памяти

Картезианское произведение (осторожно!)

@Entity
public class Author {
    @OneToMany(fetch = FetchType.EAGER)
    private List<Book> books;  // много-к-многим
    
    @OneToMany(fetch = FetchType.EAGER)
    private List<Award> awards;  // еще много-к-многим
}

// Результат запроса:
// books.size() * awards.size() дубликатов!
// Очень неэффективно

Итого

Жадная загрузка загружает все связанные данные сразу. Решает проблему N+1, но может загрузить много ненужных данных. Лучше использовать явно через @EntityGraph или JOIN FETCH, а не полагаться на FetchType.EAGER на аннотациях отношений.