Какой FetchType по умолчанию в Hibernate?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
FetchType по умолчанию в Hibernate
В Hibernate существует два типа загрузки связанных объектов: LAZY и EAGER. Выбор правильного FetchType критичен для производительности приложения.
Короткий ответ
FetchType по умолчанию зависит от типа связи:
- @OneToMany и @ManyToMany → LAZY (ленивая загрузка)
- @OneToOne и @ManyToOne → EAGER (активная загрузка)
Это один из частых источников проблем производительности!
Детальное объяснение
LAZY (ленивая загрузка)
Данные загружаются только тогда, когда к ним впервые обращаются.
@Entity
public class User {
@Id
private Long id;
private String name;
// Связь OneToMany - по умолчанию LAZY
@OneToMany(mappedBy = "user")
private List<Order> orders; // Загружается ЛЕНИВО
}
@Entity
public class Order {
@Id
private Long id;
@ManyToOne // По умолчанию EAGER!
private User user; // Загружается СРАЗУ
}
// Пример
User user = userRepository.findById(1L); // SELECT * FROM User WHERE id=1
List<Order> orders = user.getOrders(); // SELECT * FROM Order WHERE user_id=1 (запрос выполняется здесь!)
EAGER (активная загрузка)
Данные загружаются сразу вместе с родительским объектом.
@Entity
public class Order {
@Id
private Long id;
// По умолчанию EAGER
@ManyToOne(fetch = FetchType.EAGER)
private User user; // Загружается вместе с Order
}
// Пример
Order order = orderRepository.findById(1L);
// SELECT o.*, u.* FROM Order o LEFT JOIN User u ON o.user_id = u.id WHERE o.id=1
User user = order.getUser(); // user уже загружен, нет дополнительного запроса
По умолчанию в JPA/Hibernate
// 1. @OneToMany - LAZY по умолчанию
@Entity
public class Department {
@OneToMany(mappedBy = "department")
private List<Employee> employees; // Загружается лениво
}
// 2. @ManyToMany - LAZY по умолчанию
@Entity
public class Student {
@ManyToMany
@JoinTable(name = "student_course")
private List<Course> courses; // Загружается лениво
}
// 3. @ManyToOne - EAGER по умолчанию (ВНИМАНИЕ!)
@Entity
public class Order {
@ManyToOne // EAGER - опасно!
private Customer customer; // Загружается активно
}
// 4. @OneToOne - EAGER по умолчанию
@Entity
public class Account {
@OneToOne
private User user; // Загружается активно
}
Проблемы с EAGER по умолчанию
Проблема 1: N+1 SELECT
// ❌ Проблема
@Entity
public class Order {
@Id
private Long id;
@ManyToOne(fetch = FetchType.EAGER) // EAGER загружает автоматически
private Customer customer;
}
// Используемый код
List<Order> orders = orderRepository.findAll(); // SELECT * FROM Order (1 запрос)
// Для каждого Order загружается Customer (N запросов!)
// Итого: 1 + N запросов!
Проблема 2: Циклические загрузки
// ❌ Циклическая загрузка
@Entity
public class Order {
@ManyToOne(fetch = FetchType.EAGER)
private Customer customer;
}
@Entity
public class Customer {
@OneToMany(mappedBy = "customer", fetch = FetchType.EAGER) // Плохо!
private List<Order> orders;
}
// Попытка сериализовать → бесконечная рекурсия
Правильное использование FetchType
Рекомендация 1: Используй LAZY по умолчанию
// ✅ Хорошо
@Entity
public class Order {
@Id
private Long id;
// Явно указываем LAZY для ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
private Customer customer;
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<OrderItem> items;
}
Рекомендация 2: Используй EAGER только когда нужно
// ✅ EAGER только когда данные точно понадобятся
@Entity
public class Account {
@Id
private Long id;
// Если Account всегда используется с User
@OneToOne(fetch = FetchType.EAGER)
private User user;
}
Рекомендация 3: Используй JOIN FETCH запрос
// ✅ Контролируемая загрузка
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("SELECT o FROM Order o JOIN FETCH o.customer WHERE o.id = :id")
Order findByIdWithCustomer(@Param("id") Long id);
}
// SELECT o.*, c.* FROM Order o JOIN Customer c ON o.customer_id = c.id
Рекомендация 4: Используй DTO для контролируемого выбора полей
// ✅ DTO запрос - загружаешь только нужные данные
@Query(
"SELECT new com.example.OrderDTO(o.id, o.total, c.name) " +
"FROM Order o " +
"LEFT JOIN o.customer c " +
"WHERE o.id = :id"
)
OrderDTO findOrderDTOById(@Param("id") Long id);
public class OrderDTO {
private Long id;
private BigDecimal total;
private String customerName;
// конструктор и геттеры
}
Практический пример: Правильная конфигурация
// ❌ Плохой дизайн
@Entity
public class BadOrder {
@Id
private Long id;
@ManyToOne(fetch = FetchType.EAGER) // Зачем?
private Customer customer;
@OneToMany(fetch = FetchType.EAGER) // Очень плохо!
private List<OrderItem> items;
@ManyToOne(fetch = FetchType.EAGER) // Слишком много EAGER
private Warehouse warehouse;
}
// ✅ Хороший дизайн
@Entity
public class GoodOrder {
@Id
private Long id;
private Long customerId; // Или LAZY загрузка
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customerId")
private Customer customer;
// Список всегда LAZY
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<OrderItem> items;
@ManyToOne(fetch = FetchType.LAZY)
private Warehouse warehouse;
}
Тестирование FetchType
// Проверяем, сколько SQL запросов выполняется
@Test
@DisplayName("Проверяем, что связь загружается лениво")
void testLazyLoading() {
// Настраиваем отслеживание SQL
SQLStatementCountValidator.reset();
Order order = orderRepository.findById(1L);
// 1 запрос: SELECT * FROM Order
assertThat(SQLStatementCountValidator.assertSelectCount()).isOne();
// Обращаемся к customer - новый запрос
order.getCustomer().getName();
// 2 запроса: SELECT * FROM Customer
assertThat(SQLStatementCountValidator.assertSelectCount()).isEqualTo(2);
}
Hibernate статистика
// Включи статистику для анализа
persistence.xml:
<property name="hibernate.generate_statistics" value="true" />
<property name="hibernate.use_sql_comments" value="true" />
// Анализируй запросы
Statistics stats = sessionFactory.getStatistics();
stats.logSummary();
stats.getQueryExecutionCount();
Таблица FetchType по умолчанию
| Аннотация | FetchType по умолчанию | Рекомендация |
|---|---|---|
| @OneToMany | LAZY | Оставляй LAZY |
| @ManyToMany | LAZY | Оставляй LAZY |
| @OneToOne | EAGER | Измени на LAZY если возможно |
| @ManyToOne | EAGER | ⚠️ Измени на LAZY |
Заключение
По умолчанию @ManyToOne и @OneToOne используют EAGER загрузку, а @OneToMany и @ManyToMany используют LAZY. Это может привести к N+1 проблемам и проблемам производительности.
Золотое правило: начни с LAZY везде, потом явно устанавливай EAGER или используй JOIN FETCH только для специфичных запросов, где ты точно знаешь, что нужны связанные данные.