что такое каскады hibernate
Hibernate. Основные принципы работы с сессиями и транзакциями
В моей первой статье на Хабре я хотел бы поделиться некоторыми соображениями и замечаниями по работе с Hibernate, касающихся сессий и транзакций. Я остановился на некоторых нюансах, которые возникают при начале освоения этой темы. Признаюсь, сам пока Junior-программист, с Hibernate работал не постоянно, поэтому, как всегда, возможны ошибки, коль заметите оные, буду благодарен за поправки.
Библиотека Hibernate является самой популярной ORM-билиотекой и реализацией Java Persistence API. Часто используется как ORM-провайдер в обычных Java-приложениях, контейнерах сервлетов, в частности, в сервере приложений JBoss (и его потомке WildFly).
1). Объекты-сущности (Entity Objects)
Рассмотрим две сущности — пользователя и его задачи:
Теперь приведём классы-сущности для этих таблиц:
Об аннотациях JPA можно прочитать здесь.
2). Интерфейс Session
The main runtime interface between a Java application and Hibernate. This is the central API class abstracting the notion of a persistence service.
The lifecycle of a Session is bounded by the beginning and end of a logical transaction. (Long transactions might span several database transactions.)
The main function of the Session is to offer create, read and delete operations for instances of mapped entity classes.
Интерфейс org.hibernate.Session является мостом между приложением и Hibernate. С помощью сессий выполняются все CRUD-операции с объектами-сущностями. Объект типа Session получают из экземпляра типа org.hibernate.SessionFactory, который должен присутствовать в приложении в виде singleton.
3). Состояния объектов
Объект-сущность может находиться в одном из 3-х состояний (статусов):
А теперь обратим внимание на аннотации @OneToMany и @ManyToOne в классах-сущностях. Параметр fetch в @OneToMany обозначает, когда загружать дочерние объекты. Может иметь одно из двух значений, указанных в перечислении javax.persistence.FetchType:
FetchType.EAGER — загружать коллекцию дочерних объектов сразу же, при загрузке родительских объектов.
FetchType.LAZY — загружать коллекцию дочерних объектов при первом обращении к ней (вызове get) — так называемая отложенная загрузка.
Параметр cascade обозначает, какие из методов интерфейса Session будут распространяться каскадно к ассоциированным сущностям. Например, в классе-сущности User для коллекции tasks укажем:
Тогда при выполнении session.persist(user) или session.merge(user) операции persist или merge будут применены ко всем объектам из tasks. Аналогично для остальных операций из перечисления javax.persistence.CascadeType. CascadeType.ALL применяет все операции из перечисления. Необходимо правильно настроить CascadeType, дабы не подгружать из базы кучу лишних ассоциированных объектов-сущностей.
4). Извлечение объектов из БД
Приведём простой пример:
Вместо метода session.get() можно использовать session.load(). Метод session.load() возвращает так называемый proxy-object. Proxy-object — это объект-посредник, через который мы можем взаимодействовать с реальным объектом в БД. Он расширяет функционал объекта-сущности. Взаимодействие с proxy-object полностью аналогично взаимодействию с объектом-сущностью. Proxy-object отличается от объекта-сущности тем, что при создании proxy-object не выполняется ни одного запроса к БД, т. е. Hibernate просто верит нам, что объект с данным Id существует в БД. Однако первый вызванный get или set у proxy-object сразу инициирует запрос select, и если объекта с данным Id нет в базе, то мы получим ObjectNotFoundException. Основное предназначение proxy-object — реализация отложенной загрузки.
Вызов user.getTasks() инициирует загрузку задач юзера из БД, так как в классе User для tasks установлен FetchType.LAZY.
LazyInitializationException
С параметром FetchType.LAZY нужно быть аккуратнее. Иногда при загрузке ассоциированных сущностей мы можем поймать исключение LazyInitializationException. В вышеуказанном коде во время вызова user.getTasks() user должен быть либо в статусе persistent, либо proxy.
Также LazyInitializationException может вызвать небольшое изменение в нашем коде:
Здесь теоретически всё верно. Но при попытке обращения к tasksList мы МОЖЕМ получить LazyInitializationException. Но в дебагере данный код отрабатывает верно. Почему? Потому, что user.getTasks() только возвращает ссылку на коллекцию, но не ждёт её загрузки. Не подождав, пока загрузятся данные, мы закрыли сессию. Выход — выполнять в транзакции, т. е.:
Выборка с условиями
А теперь приведём несколько простых примеров выборки данных с условиями. Для этого в Hibernate используются объекты типа org.hibernate.Criteria:
Здесь понятно, что мы выполняем select * from user where login=’login’. В метод add мы передаём объект типа Criterion, представляющий определённый критерий выборки. Класс org.hibernate.criterion.Restrictions предоставляет множество различных видов критериев. Параметр «login» обозначает название свойства класса-сущности, а не поля в таблице БД.
Приведём ещё пару примеров:
Здесь мы выбираем по содержимому свойства name класса-сущности Task. MatchMode.ANYWHERE означает, что нужно искать подстроку name в любом месте свойства «name».
б).
А здесь мы получаем 50 строк, начиная с 20-го номера в таблице.
5). Сохранение объектов
Давайте разберём несколько способов сохранения объекта-сущности в базу данных.
а). Создаём transient-object и сохраняем в базу:
Отметим несколько нюансов. Во-первых, сохранение в БД можно производить только в рамках транзакции. Вызов session.openTransaction() открывает для данной сессии новую транзакцию, а session.getTransaction().commit() её выполняет. Во-вторых, в метод task.setUser(user) мы передаём user в статусе detached. Можно передать и в статусе persistent.
Данный код выполнит (не считая получения user) 2 запроса — select nextval(‘task_task_id_seq’) и insert into task.
Вместо saveOrUpdate() можно выполнить save(), persist(), merge() — будет также 2 запроса. Вызов session.flush() применяет все изменения к БД, но, если честно, этот вызов здесь бесполезен, так как ничего не сохраняется в БД до commit(), который сам вызовет flush().
Помним, что если мы внутри транзакции что-то изменим в загруженном из БД объекте статуса persistent или proxy-object, то выполнится запрос update. Если task должен ссылаться на нового user, то делаем так:
Внимание: в классе Task для поля user должен быть установлен CascadeType.PERSIST, CascadeType.MERGE или CascadeType.ALL.
Если мы имеем на руках userId существующего в БД юзера, то нам не обязательно загружать объект User из БД, делая лишний select. Так как мы не можем присвоить ID юзера непосредственно свойству класса Task, нам нужно создать объект класса User с единственно заполненными userId. Естественно, это не может быть transient-object, поэтому здесь следует воспользоваться известным нам proxy-объектом.
б). Добавляем объект в коллекцию дочерних объектов:
В User для свойства tasks должен стоять CascadeType.ALL. Если стоит CascadeType.MERGE, то после user.getTasks().add(task) выполнить session.merge(user). Данный код выполнит 3 запроса — select * from user, select nextval(‘task_task_id_seq’) и insert into task…
6). Удаление объектов
а). Можно удалить, создав transient-object:
Данный код удалит только task. Однако, если task — объект типа proxy, persistent или detached и в классе Task для поля user действует CascadeType.REMOVE, то из базы удалится также ассоциированный user. Если удалять юзера не нужно, выполнить что? Правильно, task.setUser(null)
б). Можно удалить и таким способом:
Данный код просто удаляет связь между task и user. Здесь мы применили новомодное лямбда-выражение. Объект task удалится из БД при одном условии — если изменить кое-что в классе-сущности User:
Параметр orphanRemoval = true указывает, что все объекты Task, которые не имеют ссылки на User, должны быть удалены из БД.
7). Декларативное управление транзакциями
Для декларативного управления транзакциями мы будем использовать Spring Framework. Управление транзакциями осуществляется через менеджер транзакций. Вместо вызовов session.openTransaction() и session.commit() используется аннотация @Transactional. В конфигурации приложения должно присутствовать следующее:
Здесь мы определили бин transactionManager, к которому привязан бин sessionFactory. Класс HibernateTransactionManager является реализацией общего интерфейса org.springframework.transaction.PlatformTransactionManager для SessionFactory библиотеки Hibernate. annotation-driven указывает менеджеру транзакций обрабатывать аннотацию @Transactional.
— Болтовня ничего не стоит. Покажите мне код. (Linus Torvalds)
Аннотация @Transactional указывает, что метод должен выполняться в транзакции. Менеджер транзакций открывает новую транзакцию и создаёт для неё экземпляр Session, который доступен через sessionFactory.getCurrentSession(). Все методы, которые вызываются в методе с данной аннотацией, также имеют доступ к этой транзакции, потому что экземпляр Session является переменной потока (ThreadLocal). Вызов sessionFactory.openSession() откроет совсем другую сессию, которая не связана с транзакцией.
Параметр rollbackFor указывает исключения, при выбросе которых должен быть произведён откат транзакции. Есть обратный параметр — noRollbackFor, указывающий, что все исключения, кроме перечисленных, приводят к откату транзакции.
Параметр propagation самый интересный. Он указывает принцип распространения транзакции. Может принимать любое значение из перечисления org.springframework.transaction.annotation.Propagation. Приведём пример:
Метод UserDao.getUserByLogin() также может быть помечен аннотацией @Transactional. И здесь параметр propagation определит поведение метода UserDao.getUserByLogin() относительно транзакции метода saveTask():
Ну что ж, подведём итоги
В моей статье я осветил самые основные принципы работы с сессиями и транзакциями в Hibernate. Надеюсь, что начинающим Java-программистам статья будет полезна при преодолении первого порога в изучении суперклассной (не для всех, возможно) библиотеки Hibernate. Желаю всем успехов в нашей сложной и интересной программерской деятельности!
Руководство для начинающих по типам каскадов JPA и гибернации
JPA переводит переходы состояния сущности в базу данных DML операторы. Поскольку обычно используется для работы с графами сущностей, JPA позволяет нам распространять изменения состояния сущностей от Родителей к Дочерним сущностям.
Hibernate поддерживает все типы каскадов JPA и некоторые дополнительные устаревшие стили каскадирования. В следующей таблице показана связь между типами JPA Каскад и их Гибернацией собственным API эквивалентом:
Прослушиватель событий выселения по умолчанию | ОТСОЕДИНИТЬ | отсоединить(сущность) | ОТСОЕДИНИТЬ или ВЫСЕЛИТЬ | выселить(юридическое лицо) |
Прослушиватель событий слияния по умолчанию | ПОГЛОЩАТЬ | слияние(сущность) | ПОГЛОЩАТЬ | слияние(сущность) |
Прослушиватель постоянных событий по умолчанию | НАСТАИВАТЬ | сохраниться(сущность) | НАСТАИВАТЬ | сохраниться(сущность) |
Прослушиватель событий Обновления по умолчанию | ОСВЕЖИТЬ | обновить(сущность) | ОСВЕЖИТЬ | обновить(сущность) |
Прослушиватель событий Удаления по умолчанию | УДАЛИТЬ | удалить(сущность) | УДАЛИТЬ или УДАЛИТЬ | удалить(сущность) |
Прослушиватель Событий Сохранения Или Обновления По Умолчанию | СОХРАНИТЬ ОБНОВЛЕНИЕ | Сохранить обновление(сущность) | ||
Прослушиватель событий Репликации по Умолчанию | КОПИРОВАТЬ | репликация(сущность, режим репликации) | ||
Прослушиватель событий Блокировки по умолчанию | блокировка(сущность, тип блокировки) | ЗАМОК | buildLockRequest(сущность, блокировки) | |
ВСЕ | Все вышеперечисленные методы EntityManager | ВСЕ | Все вышеперечисленные методы сеанса гибернации |
Из этой таблицы мы можем сделать вывод, что:
для ПЕРЕХОДНЫХ сущностей и объединять для ОТДЕЛЕННЫХ сущностей. Недостатки saveOrUpdate
Спящий режим конкретные события. Например, вы можете каскадировать операцию блокировки JPA (хотя она ведет себя как повторное подключение, а не как фактическое распространение запроса на блокировку), даже если JPA не определяет тип каскада.ЗАМОК .
Каскадирование имеет смысл только для Родительских – Дочерних ассоциаций (переход состояния Родительской сущности каскадируется в дочерние сущности). Каскадирование из Дочернего в Родительский не очень полезно, и обычно это запах кода сопоставления.
Далее я собираюсь проанализировать каскадное поведение всех JPA | Родительских – Дочерних ассоциаций.
Один К Одному
Наиболее распространенная Взаимно однозначная двунаправленная ассоциация выглядит следующим образом:
В данном конкретном случае тип каскадный.ВСЕ и orphanremoval имеют смысл, потому что Сведения о публикации жизненный цикл привязан к Публикации Родительской сущности.
Каскадирование операции сохранения один к одному
Создание следующего вывода:
Каскадирование операции слияния один к одному
Операция слияния генерирует следующие выходные данные:
Каскадирование операции удаления один к одному
CascadeType.REMOVE также наследуется от CascadeType.ВСЕ конфигурация, поэтому удаление Записи сущности также вызывает удаление записей сущности:
Создание следующего вывода:
Каскадная операция удаления сирот один к одному
Удаление сироты генерирует этот вывод:
Однонаправленная связь “один к одному”
Чаще всего Родительская сущность является обратной стороной (например, mappedBy ), поскольку Дочерняя сущность контролирует ассоциацию через свой Внешний ключ. Но каскад не ограничивается двунаправленными ассоциациями, мы также можем использовать его для однонаправленных отношений:
Каскадирование заключается в распространении перехода состояния Родительской сущности на одну или несколько Дочерних сущностей и может использоваться как для однонаправленных, так и для двунаправленных ассоциаций.
Один Ко Многим
Наиболее распространенная Родительская – Дочерняя ассоциация состоит из один ко многим и многие к одному отношения, где каскад полезен только для один ко многим стороны:
Каскадирование операции сохранения “один ко многим”
Нам нужно только сохранить Запись сущность, и все связанные Комментарии сущности также сохраняются:
Операция сохранения генерирует следующий вывод:
Каскадирование операции слияния “один ко многим”
Слияние Записи сущности приведет к объединению всех Комментариев сущностей, а также:
Создание следующего вывода:
Каскадирование операции удаления “один ко многим”
Когда Запись сущность удаляется, связанные Комментарий сущности также удаляются:
Создание следующего вывода:
Каскадная операция “один ко многим” по удалению сироты
orphanremoval позволяет нам удалять Дочернюю сущность всякий раз, когда на нее больше не ссылается ее Родитель :
Комментарий удален, как мы видим в следующем выводе:
Многие Ко Многим
Связь многие ко многим сложна, потому что на этот раз связь отображается на родительских сторонах ассоциации, в то время как дочерняя сторона (таблица соединений) скрыта. Если связь является двунаправленной, обе стороны могут распространять изменения состояния сущности.
Мы не должны использовать по умолчанию CascadeType.ВСЕ потому что CascadeType.REMOVE может привести к удалению большего количества, чем мы ожидаем (как вы скоро узнаете):
Каскадирование операции сохранения “многие ко многим”
Сохранение Автора сущностей также сохранит Книги :
Книга и Book_Author строки вставляются вместе с Авторами :
Разъединение одной стороны ассоциации “многие ко многим”
Этот вариант использования генерирует следующие выходные данные:
Ассоциация многие ко многим генерирует слишком много избыточных SQL операторов, и часто их очень трудно настроить. Далее я собираюсь продемонстрировать много-ко-многим |/Каскадный тип.УДАЛИТЕ скрытые опасности.
Каскадный тип “многие ко многим”. УДАЛИТЕ ошибки
Каскадный тип многие ко многим |/.ВСЕ – это еще один запах кода, с которым я часто сталкиваюсь при просмотре кода. CascadeType.REMOVE автоматически наследуется при использовании CascadeType.ВСЕ , но удаление сущности применяется не только к таблице ссылок, но и к другой стороне ассоциации.
Давайте изменим ассоциацию Автор сущность книги |/многие ко многим , чтобы использовать Каскадный тип.ВСЕ вместо:
При удалении одного Автора :
Чаще всего такое поведение не соответствует ожиданиям бизнес-логики и обнаруживается только при первом удалении сущности.
Мы можем продвинуть эту проблему еще дальше, если установим Каскадный тип.ВСЕ на сторону Книги сущности, а также:
Этот вариант использования неверен во многих отношениях. Существует множество ненужных SELECT утверждений, и в конечном итоге мы удаляем всех авторов и все их книги. Вот почему Каскадный тип.ВСЕ должны поднимать брови всякий раз, когда вы замечаете это в ассоциации “многие ко многим”//.
Когда дело доходит до Спящий режим сопоставления, вы всегда должны стремиться к простоте. Документация Hibernate также подтверждает это предположение:
Hibernate JPA Cascade Types
Last Updated: December 26, 2020
We learned about mapping associated entities in hibernate already in previous tutorials such as one-to-one mapping and one-to-many mappings. There we wanted to save the mapped entity whenever relationship owner entity got saved. To enable this we had use “CascadeType” attribute. In this JPA Cascade Types tutorial, we will learn about various type of available options for cascading via CascadeType.
How JPA Cascade Types Work?
Before moving forward, let’s look at how this cascade type attribute is defined in your code. Let’s have an example for more clear understanding. Take a scenario where an Employee can have multiple Accounts; but one account must be associated with only one employee. Let’s create entities with minimum information for sake of clarity.
EmployeeEntity.java
AccountEntity.java
But what if we only want to cascade only save operations but not delete operation. Then we need to clearly specify it using below code.
Now only when save() or persist() methods are called using employee instance then only accounts will be persisted. If any other method is called on session, it’s effect will not affect/cascade to accounts.
JPA Cascade Types
The cascade types supported by the Java Persistence Architecture are as below:
There is no default cascade type in JPA. By default no operations are cascaded.
The cascade configuration option accepts an array of CascadeTypes; thus, to include only refreshes and merges in the cascade operation for a One-to-Many relationship as in our example, you might see the following:
Above cascading will cause accounts collection to be only merged and refreshed.
Hibernate Cascade Types
Now lets understand what is cascade in hibernate in which scenario we use it.
Apart from JPA provided cascade types, there is one more cascading operation in hibernate which is not part of the normal set above discussed, called “orphan removal“. This removes an owned object from the database when it’s removed from its owning relationship.
Let’s understand with an example. In our Employee and Account entity example, I have updated them as below and have mentioned “orphanRemoval = true” on accounts. It essentially means that whenever I will remove an ‘account from accounts set’ (which means I am removing the relationship between that account and Employee); the account entity which is not associated with any other Employee on database (i.e. orphan) should also be deleted.
EmployeeEntity.java
AccountEntity.java
TestOrphanRemovalCascade.java
It’s a very good way of removing the matching/mismatching items from a collection (i.e. many-to-one or one-to-many relationships). You just remove the item from collection and hibernate take care of rest of the things for you. It will check whether entity is referenced from any place or not; If it is not then it will delete the entity from database itself.
Let me know of your thoughts and questions on hibernate 5 cascade types or JPA cascade types, if any.
Обзор типов каскадов JPA/Hibernate
Краткий и практический обзор типов каскадов JPA/Hibernate.
1. введение
В этом уроке мы обсудим, что такое каскадирование в JPA/Hibernate. Затем мы рассмотрим различные доступные типы каскадов, а также их семантику.
Дальнейшее чтение:
Введение в весенние данные JPA
Сопоставление имен классов сущностей с именами таблиц SQL с помощью JPA
2. Что Такое Каскадирование?
Отношения сущностей часто зависят от существования другой сущности — например, отношения Person – |/Address . Без Person , Адрес сущность не имеет никакого собственного значения. Когда мы удаляем Лицо сущность или Адрес сущность также должны быть удалены.
Каскадирование-это способ достичь этого. Когда мы выполняем какое-либо действие над целевым объектом, то же самое действие будет применено к связанному объекту.
2.1. Каскадный тип JPA
Все каскадные операции, специфичные для JPA, представлены javax.persistence.CascadeType Перечисление, содержащее записи:
2.2. Каскадный тип гибернации
Hibernate поддерживает три дополнительных типа каскадов, а также те, которые указаны в JPA. Эти типы каскадов для гибернации доступны в org.hibernate.annotations.Каскадный тип :
ЗАМОК
3.1. Каскадный тип.ВСЕ
Каскад.ALL распространяет все операции, включая операции, связанные с гибернацией, от родительского объекта к дочернему.
Давайте рассмотрим это на примере:
Обратите внимание, что в OneToMany ассоциации, мы упомянули каскадный тип в аннотации.
Теперь давайте посмотрим на связанную сущность Адрес :
3.2. Каскадный тип.НАСТАИВАТЬ
Давайте рассмотрим тестовый случай для постоянной операции:
Когда мы запустим приведенный выше тестовый случай, мы увидим следующий SQL:
3.3. Каскадный тип.ПОГЛОЩАТЬ
Давайте протестируем операцию слияния:
Когда мы запускаем приведенный выше тестовый случай, операция слияния генерирует следующий SQL:
3.4. Каскадный тип.УДАЛИТЬ
Как следует из названия, операция удаления удаляет строку, соответствующую сущности, из базы данных, а также из постоянного контекста.
Теперь пришло время проверить CascadeType.Remove :
Когда мы запустим приведенный выше тестовый случай, мы увидим следующий SQL:
3.5. Каскадный тип.ОТСОЕДИНИТЬ
Давайте посмотрим на это в действии:
Давайте посмотрим тестовый пример, чтобы понять CascadeType.ЗАМОК :
3.7. Каскадный тип.ОБНОВЛЕНИЕ
В таком сценарии это может быть полезно. Когда мы используем эту операцию с CascadeType REFRESH , дочерняя сущность также перезагружается из базы данных всякий раз, когда обновляется родительская сущность.
Для лучшего понимания давайте рассмотрим тестовый пример для CascadeType.REFRESH :
Здесь мы внесли некоторые изменения в сохраненные сущности person и
Здесь мы внесли некоторые изменения в сохраненные сущности || person || и
Теперь давайте проверим CascadeType. РЕПЛИЦИРОВАТЬ :
Из-за CascadeType |/REPLICATE , когда мы реплицируем person entity, его связанный адрес также реплицируется с заданным нами идентификатором.
3.9. CascadeType.SAVE_UPDATE
CascadeType.SAVE_UPDATE распространяет ту же операцию на связанную дочернюю сущность. Это полезно, когда мы используем операции Hibernate, такие как save, update, и saveOrUpdate .
Давайте посмотрим CascadeType. SAVE_UPDATE в действии:
4. Заключение
В этой статье мы обсудили каскадирование и различные варианты каскадного типа, доступные в JPA и Hibernate.