Ответ
Параметр fetch в аннотации @ManyToMany определяет стратегию загрузки связанных сущностей.
| FetchType | Поведение | Когда использовать |
|---|---|---|
FetchType.EAGER (по умолчанию для @ManyToMany) |
Связанная коллекция загружается немедленно вместе с владельцем, обычно одним SQL-запросом с JOIN. |
Если связанные данные почти всегда нужны сразу при загрузке основной сущности. |
FetchType.LAZY |
Коллекция загружается только при первом обращении к ней (например, вызов getRoles()). Hibernate возвращает прокси. |
Для оптимизации производительности, когда связанные данные требуются не всегда. Это предпочтительная стратегия. |
Примеры:
// EAGER загрузка (может привести к "проблеме N+1" при неправильном использовании)
@ManyToMany(fetch = FetchType.EAGER)
private Set<Role> roles;
// LAZY загрузка (рекомендуется)
@ManyToMany(fetch = FetchType.LAZY)
private Set<Role> roles;
Ключевые нюансы:
LazyInitializationException: При LAZY-загрузке обращение к коллекции вне контекста открытой сессии/транзакции выбросит это исключение. Решение: использоватьJOIN FETCHв запросе или открыть транзакцию на время чтения.- Производительность: EAGER может привести к избыточной загрузке данных и падению производительности. LAZY позволяет контролировать загрузку, но требует аккуратной работы с сессией.
Ответ 18+ 🔞
Давай разберём эту хуйню про fetch в @ManyToMany, а то народ путается, как последние идиоты.
Смотри, есть у тебя, допустим, сущность User и у него роли — Set<Role>. Так вот, когда ты тянешь юзера из базы, вопрос в том, тащить ли заодно все его роли сразу или подождать, пока они реально понадобятся. Вот за эту дилемму и отвечает параметр fetch.
FetchType.EAGER (жадный)
Это как обжора в столовой — хватает всё сразу, нахуй. Загрузил юзера — автоматом вывалил и все его роли в одном запросе, обычно с JOIN.
@ManyToMany(fetch = FetchType.EAGER)
private Set<Role> roles;
Когда это пиздато? Ну, если тебе эти роли нужны всегда, блядь, в 95% случаев. Но если нет — получишь овердохуища лишних данных и просадку производительности. Это дефолт для @ManyToMany, и это, по мнению многих, ебанутое решение.
FetchType.LAZY (ленивый)
А это уже хитрая жопа. Говорит: «Юзера загрузил, а роли — похуй, буду тянуть только когда в них полезешь». При первом вызове getRoles() Hibernate под капотом дергает базу и возвращает тебе коллекцию.
@ManyToMany(fetch = FetchType.LAZY)
private Set<Role> roles;
Это рекомендуемая стратегия, ёпта. Оптимизация, контроль, меньше мусора в памяти. Но и подводный ебун — LazyInitializationException.
А вот и главный подвох, нахуй!
Если ты используешь LAZY и пытаешься достучаться до коллекции уже после закрытия сессии или транзакции (например, в JSF-бинде или REST-контроллере, где сессия уже прикрыта), Hibernate тебе любезно подкинет LazyInitializationException. Мол, сам дурак, где был раньше?
Как не облажаться:
JOIN FETCHв запросе — самый частый способ. Говоришь Hibernate: «На этом конкретном запросе загрузи мне всё жадно, я знаю, что делаю».@Query("SELECT u FROM User u JOIN FETCH u.roles WHERE u.id = :id") User findUserWithRoles(@Param("id") Long id);- Открыть транзакцию на время чтения — если архитектура позволяет.
- Изменить
FetchTypeнаEAGER— но это тупой костыль, который потом выстрелит в другом месте. Не делай так просто чтобы «исключение убрать».
Итог, блядь:
По умолчанию ставь LAZY. Это как поставить на сигнализацию — лучше перебдеть. А когда точно знаешь, что нужно всё и сразу — явно жарь JOIN FETCH в запросе. И следи, чтобы сессия была открыта в момент обращения к ленивой коллекции, иначе будет тебе LazyInitializationException прямо в ебальник. Всё, вопрос закрыт.