Какие типы ассоциаций существуют в Hibernate: @OneToOne, @OneToMany, @ManyToOne, @ManyToMany?
ManyToOne — owner FK, OneToMany — inverse через mappedBy, OneToOne — уникальный FK, ManyToMany — join-таблица. По умолчанию ManyToOne/OneToOne EAGER — всегда переключайте на LAZY.
Типы ассоциаций в Hibernate
Hibernate поддерживает четыре типа ассоциаций, соответствующих отношениям между таблицами в реляционной БД. Каждый тип определяет, сколько записей одной сущности связано с записями другой, и влияет на то, какой стороне принадлежит foreign key.
@ManyToOne
Наиболее распространённый тип. Сторона @ManyToOne является owner ассоциации — именно в её таблице хранится foreign key. Hibernate генерирует колонку автоматически или её можно переопределить через @JoinColumn.
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id", nullable = false)
private Customer customer;
}
По умолчанию fetch-стратегия EAGER — это частая ловушка. Всегда явно указывайте FetchType.LAZY.
@OneToMany
Обратная сторона @ManyToOne. Как правило, является inverse стороной через mappedBy, что означает: эта сторона не управляет foreign key, а лишь отражает связь. Без mappedBy Hibernate создаст отдельную join-таблицу.
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
// Обязательно поддерживайте обе стороны вручную
public void addOrder(Order order) {
orders.add(order);
order.setCustomer(this);
}
}
@OneToOne
Связь один-к-одному. Owner стороны содержит foreign key с UNIQUE constraint. Inverse сторона использует mappedBy.
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "profile_id", unique = true)
private UserProfile profile;
}
@Entity
public class UserProfile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(mappedBy = "profile")
private User user;
}
@ManyToMany
Реализуется через промежуточную join-таблицу. Hibernate управляет ею автоматически через @JoinTable. Однако в production почти всегда лучше заменить @ManyToMany явной сущностью-связкой с дополнительными атрибутами (дата, статус и т.д.).
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
}
Подводные камни
- FetchType.EAGER по умолчанию в
@ManyToOneи@OneToOneприводит к N+1 проблеме при обходе коллекций. Всегда используйтеLAZYи загружайте через JOIN FETCH при необходимости. - Отсутствие mappedBy в
@OneToManyбез явной@JoinTableзаставляет Hibernate создать скрытую join-таблицу вместо использования FK в дочерней таблице. - Несинхронизированные обе стороны: при двунаправленной связи изменение только одной стороны в памяти ведёт к некорректному состоянию первого уровня кэша до flush.
- @ManyToMany с List вместо Set вызывает полное удаление и повторную вставку всех записей join-таблицы при любом изменении коллекции — используйте
Set. - CascadeType.ALL на @ManyToMany опасен: удаление одного студента может каскадно удалить общие курсы.
- equals/hashCode: без корректной реализации на основе бизнес-ключа (не
id) поведениеSetдля managed/detached сущностей непредсказуемо. - Cartesian product: одновременная fetch-загрузка нескольких
@OneToManyколлекций через JOIN FETCH порождает декартово произведение строк — используйте@BatchSizeили несколько запросов.
Common mistakes
- Путать термин «association types» с соседним механизмом Hibernate.
- Не называть границу lifecycle, transaction, thread или request для «association types».
- Игнорировать production-эффекты «association types»: latency, SQL shape, memory, security или observability.
What the interviewer is testing
- Попросить объяснить механизм «association types» на минимальном примере.
- Проверить, видит ли кандидат failure mode и диагностику для «association types».
- Уточнить, какие настройки или API меняют «association types» в реальном сервисе.