Hibernate: Продвинутый уровень
1. Состояния Entity (Жизненный цикл)
Объект в Hibernate — это не просто данные, это объект с состоянием. Их три:
* Transient (Временный): Объект создан через `new User()`, но Session о нем не знает. В БД его нет.
* Persistent (Постоянный): Объект прикреплен к Session (`session.save(user)` или `session.get(User.class, id)`). Любое изменение этого объекта отслеживается (Dirty Checking) и при коммите улетит в БД.
* Detached (Отсоединенный): Объект был Persistent, но Session закрылась. Он жив в памяти, но Hibernate за ним больше не следит. Чтобы вернуть его в игру, используют `merge()`.
2. Методы get() vs load() (Классика жанра)
Разница в том, как они ходят в базу:
* `get()`: Идет в БД **сразу**. Если объекта нет — возвращает `null`.
* `load()`: Возвращает **Прокси-объект** (заглушку) сразу. В БД идет только тогда, когда вы попытаетесь обратиться к полям объекта (Lazy Initialization). Если объекта нет — при обращении к полям кинет `ObjectNotFoundException`.
* Совет: Используйте `get()`, если не уверены, что объект есть. Используйте `load()`, если уверены (например, для установки связи `order.setUser(user)`).
3. save() vs persist() vs saveOrUpdate()
Это главная боль Junior-разработчиков.
* `persist()`: Делает объект Persistent. Работает только внутри транзакции. Не делает `INSERT` сразу (может сделать позже при flush). Гарантирует соблюдение спецификации JPA.
* `save()`: Старое Hibernate-API. Возвращает `Serializable` (ID объекта). Может сделать `INSERT` сразу (вне транзакции), что ломает логику JPA.
* `saveOrUpdate()`: Устаревший метод. Если ID есть — делает `UPDATE`, если нет — `INSERT`. Сейчас лучше использовать `merge()`.
4. Кэширование (Уровни кэша)
* L1 Cache (Session Cache): Есть у каждой Session по умолчанию. Если в рамках одной сессии вы дважды попросили юзера с `id=1`, Hibernate пойдет в БД только один раз. Очищается при закрытии Session.
* L2 Cache (SessionFactory Cache): Общий для всех сессий. Нужен, если данные читаются часто, а меняются редко (например, справочники: города, страны). Настраивается через провайдеры (EhCache, Redis).
5. Lazy Loading и Проблема N+1
* Lazy: Связанные коллекции (`@OneToMany`) не грузятся из БД, пока вы к ним не обратитесь. Это экономит память.
* Проблема N+1: У вас есть 100 заказов. В цикле вы делаете `order.getItems()()`. Hibernate делает 1 запрос, чтобы найти 100 заказов, и еще 100 запросов, чтобы для каждого заказа найти его товары. Итого 101 запрос.
* Решение: `JOIN FETCH` в HQL (`FROM Order o JOIN FETCH o.items`) или `@EntityGraph`.
6. Flush и Commit
* `flush()`: Выгружает изменения из Session в БД (генерирует SQL), но **не завершает транзакцию**. Если после `flush()` случится ошибка, можно откатить.
* `commit()`: Завершает транзакцию. Обычно внутри себя делает `flush()`.
* Зачем вручную? Если вам нужно получить ID объекта *до* коммита (например, чтобы записать его в лог или передать в другой сервис).
Итог:
Hibernate кажется магией, пока вы не поймете, что это просто умный SQL-генератор, который держит граф объектов в памяти. Как только вы поймете, как работает Session и Proxy, 90% багов исчезнут.