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

Что такое Lazy в Hibernate?

1.7 Middle🔥 161 комментариев
#ORM и Hibernate

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

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

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

Lazy Loading в Hibernate — Отложенная загрузка связанных объектов

Lazy Loading (ленивая загрузка) — это стратегия в Hibernate, когда связанные объекты загружаются только при первом обращении к ним, а не сразу с основным объектом. Это оптимизирует производительность и экономит память.

Проблема без Lazy Loading

Без ленивой загрузки при запросе пользователя загружаются ВСЕ его заказы, адреса и другие связанные данные:

// Если lazy = false, загрузятся ВСЕ связи
@Entity
public class User {
    @Id
    private Long id;
    private String name;
    
    @OneToMany(fetch = FetchType.EAGER)  // Загрузить ВСЕ заказы сразу
    private List<Order> orders;  // Даже если нам они не нужны!
}

Это может привести к:

  • Излишней загрузке памяти
  • Медленным запросам (N+1 problem)
  • Циклическим ссылкам между объектами

Решение: Lazy Loading

@Entity
public class User {
    @Id
    private Long id;
    private String name;
    
    @OneToMany(fetch = FetchType.LAZY)  // Загружать только при обращении
    private List<Order> orders;  // Загрузятся позже
}

Теперь заказы загружаются ТОЛЬКО когда вы обращаетесь к ним:

User user = session.find(User.class, 1L);  // Загружается только User
System.out.println(user.getName());  // Работает

// Первое обращение к orders — выполняется дополнительный запрос
List<Order> orders = user.getOrders();  // SELECT * FROM orders WHERE user_id = 1
for (Order order : orders) {
    System.out.println(order.getId());
}

Как это работает: Proxy объекты

Вместо реального объекта Hibernate создаёт proxy (прокси) — заменитель, который загружает данные по требованию:

User user = session.find(User.class, 1L);
// Реально загружено: User с id, name, но orders = LazyInitializationProxy

if (user.getOrders() != null) {  // Вызов getOrders() → выполняется запрос
    // SELECT * FROM orders WHERE user_id = 1
}

Типы связей и их defaults

АннотацияDefaultРекомендация
@ManyToOneEAGERОставить EAGER (часто нужен родитель)
@OneToOneEAGERМенять на LAZY если не всегда нужен
@OneToManyLAZYОставить LAZY
@ManyToManyLAZYОставить LAZY
@Entity
public class Order {
    @Id
    private Long id;
    private String description;
    
    @ManyToOne(fetch = FetchType.EAGER)  // Пользователь нужен почти всегда
    private User user;
    
    @ManyToMany(fetch = FetchType.LAZY)  // Товары нужны не всегда
    private List<Product> products;
}

Проблема: LazyInitializationException

Если попытаться получить lazy объект после закрытия сессии — ошибка:

Session session = sessionFactory.openSession();
User user = session.find(User.class, 1L);
session.close();  // Сессия закрыта

user.getOrders();  // ❌ LazyInitializationException!

Решения проблемы

1. Загрузить данные внутри сессии

Session session = sessionFactory.openSession();
User user = session.find(User.class, 1L);
user.getOrders().size();  // Загрузить внутри сессии
session.close();

System.out.println(user.getOrders());  // Теперь работает

2. Использовать FetchType.EAGER (если часто нужны данные)

@Entity
public class User {
    @OneToMany(fetch = FetchType.EAGER)
    private List<Order> orders;
}

3. Использовать JOIN FETCH в HQL/JPQL

Session session = sessionFactory.openSession();
Query query = session.createQuery(
    "SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders WHERE u.id = :id"
);
query.setParameter("id", 1L);
User user = (User) query.uniqueResult();
session.close();

user.getOrders().size();  // Работает! Данные загружены в первом запросе

4. Использовать @Transactional (Spring)

@Service
public class UserService {
    @Transactional
    public User getUserWithOrders(Long id) {
        User user = userRepository.findById(id).orElse(null);
        user.getOrders().size();  // Загрузить внутри транзакции
        return user;
    }
}

Spring Data JPA и Lazy Loading

public interface UserRepository extends JpaRepository<User, Long> {
    
    // Явно загрузить все нужные связи
    @Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders WHERE u.id = :id")
    User findByIdWithOrders(@Param("id") Long id);
}

Best Practices

  1. По умолчанию используй LAZY — загружай только нужное
  2. Используй JOIN FETCH — чтобы избежать N+1 проблемы
  3. Минимизируй циклические ссылки — используй @JsonIgnore в REST контроллерах
  4. Профилируй запросы — смотри сгенерированный SQL (включи show_sql=true)

Вывод

Lazy Loading — это инструмент для оптимизации, но требует понимания жизненного цикла сессий и транзакций. Неправильное использование может привести к N+1 проблемам и LazyInitializationException, но правильное применение значительно улучшит производительность приложения.

Что такое Lazy в Hibernate? | PrepBro