У меня есть сохраненная в JPA объектная модель, которая содержит отношение «многие к одному»: у « Account
есть много» Transactions
. А Transaction
есть один Account
.
Вот фрагмент кода:
@Entity
public class Transaction {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
private Account fromAccount;
....
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
private Set<Transaction> transactions;
Я могу создать Account
объект, добавить к нему транзакции и Account
правильно сохранить объект. Но когда я создаю транзакцию, используя существующую уже сохраненную учетную запись и сохраняя транзакцию , я получаю исключение:
Вызывается: org.hibernate.PersistentObjectException: отсоединенная сущность передана для сохранения: com.paulsanwald.Account at org.hibernate.event.internal.DefaultPersistEventListener.onPersist (DefaultPersistEventListener.java:141)
Таким образом, я могу сохранить Account
транзакцию, которая содержит транзакции, но не транзакцию, которая имеет Account
. Я думал, что это потому, что, Account
возможно, не прилагается, но этот код все еще дает мне то же исключение:
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
// the below fails with a "detached entity" message. why?
entityManager.persist(transaction);
Как правильно сохранить объект Transaction
, связанный с уже сохраненным Account
объектом?
Ответы:
Это типичная проблема двунаправленной согласованности. Это хорошо обсуждается в этой ссылке, а также в этой ссылке.
Согласно статьям в двух предыдущих ссылках, вам необходимо установить сеттеры по обе стороны двунаправленных отношений. Пример сеттера для One side находится по этой ссылке.
Пример сеттера для стороны Many находится в этой ссылке.
После того, как вы исправили свои установщики, вы хотите объявить тип доступа Entity как «Свойство». Рекомендуется объявлять тип доступа «Свойство» - перемещать ВСЕ аннотации из свойств элемента в соответствующие средства получения. Большое предостережение - не смешивать типы доступа «Поле» и «Свойство» в классе сущности, иначе поведение не определено спецификациями JSR-317.
источник
detached entity passed to persist
почему улучшение согласованности делает его работоспособным? Хорошо, согласованность была исправлена, но объект все еще отсоединен.Решение простое, просто используйте
CascadeType.MERGE
вместоCascadeType.PERSIST
илиCascadeType.ALL
.У меня была такая же проблема, и
CascadeType.MERGE
она работала на меня.Я надеюсь, что вы отсортированы.
источник
Удалить каскад из дочерней сущности
Transaction
, это должно быть просто:(
FetchType.EAGER
может быть удалено, так как это по умолчанию@ManyToOne
)Вот и все!
Зачем? Говоря «каскадировать ВСЕ» на дочернем объекте,
Transaction
вы требуете, чтобы каждая операция БД распространялась на родительский объектAccount
. Если вы это сделаетеpersist(transaction)
,persist(account)
также будет вызван.Но только временные (новые) объекты могут быть переданы
persist
(Transaction
в этом случае). Отсоединенные (или другие не переходные состояния) могут не (Account
в этом случае, поскольку это уже в БД).Поэтому вы получаете исключение «отдельная сущность передана для сохранения» .
Account
Объект имел в виду! Не то, чтоTransaction
вы называетеpersist
.Как правило, вы не хотите размножаться от ребенка к родителю. К сожалению, есть много примеров кода в книгах (даже в хороших) и в сети, которые делают именно это. Я не знаю, почему ... Может быть, иногда просто копировать снова и снова, не задумываясь ...
Угадайте, что произойдет, если вы по-
remove(transaction)
прежнему называете «каскадом ВСЕ» в этом @ManyToOne?account
(Кстати, со всеми другими транзакциями!) Будут удалены из базы данных , а также. Но это не было твоим намерением, не так ли?источник
Использование слияния рискованно и сложно, так что это грязный обходной путь в вашем случае. Вы должны помнить, по крайней мере, что, когда вы передаете объект сущности для слияния, он перестает быть присоединенным к транзакции, и вместо этого возвращается новая, теперь присоединенная сущность. Это означает, что если у кого-то еще есть старый объект сущности, его изменения игнорируются и выбрасываются при фиксации.
Вы не видите полный код здесь, поэтому я не могу дважды проверить ваш шаблон транзакции. Один из способов попасть в такую ситуацию - это если у вас нет активной транзакции при выполнении слияния и сохраниться. В этом случае ожидается, что поставщик сохраняемости будет открывать новую транзакцию для каждой выполняемой вами операции JPA, немедленно фиксировать и закрывать ее до возврата вызова. Если это так, то слияние будет выполнено в первой транзакции, а затем после возврата метода слияния транзакция будет завершена и закрыта, а возвращенный объект теперь отсоединен. Постоянное значение ниже этого откроет вторую транзакцию и попытается сослаться на отсоединенную сущность, выдав исключение. Всегда заключайте код в транзакцию, если вы не очень хорошо знаете, что делаете.
При использовании управляемой контейнером транзакции это будет выглядеть примерно так. Обратите внимание: это предполагает, что метод находится внутри сессионного компонента и вызывается через локальный или удаленный интерфейс.
источник
@Transaction(readonly=false)
на уровне обслуживания . Я все еще получаю ту же проблему,Вероятно, в этом случае вы получили свой
account
объект с помощью логики слияния, иpersist
он используется для сохранения новых объектов, и он будет жаловаться, если в иерархии уже есть сохраненный объект. Вы должны использоватьsaveOrUpdate
в таких случаях, а неpersist
.источник
merge
наtransaction
объекте вы получаете ту же ошибку?transaction
это не сохранилось? Как ты проверил Обратите внимание, чтоmerge
возвращается ссылка на постоянный объект.Не передавайте id (pk) методу persist или попробуйте метод save () вместо persist ().
источник
TestEntityManager.persistAndFlush()
, чтобы сделать экземпляр управляемым и постоянным, а затем синхронизировать контекст постоянства с базовой базой данных. Возвращает исходный объектДля решения проблемы вам необходимо выполнить следующие шаги:
1. Удаление каскадирования детских ассоциаций
Итак, вам нужно удалить
@CascadeType.ALL
из@ManyToOne
ассоциации. Дочерние объекты не должны каскадироваться с родительскими ассоциациями. Только родительские объекты должны каскадироваться к дочерним объектам.Обратите внимание, что я установил
fetch
атрибут,FetchType.LAZY
потому что активная выборка очень плохо влияет на производительность .2. Установите обе стороны ассоциации
Всякий раз, когда у вас есть двунаправленная связь, вам нужно синхронизировать обе стороны, используя
addChild
иremoveChild
методы в родительской сущности:Чтобы узнать больше о том, почему важно синхронизировать оба конца двунаправленной ассоциации, ознакомьтесь с этой статьей .
источник
В определении вашей сущности вы не указываете @JoinColumn для
Account
присоединяемого кTransaction
. Вы хотите что-то вроде этого:РЕДАКТИРОВАТЬ: Ну, я думаю, это было бы полезно, если бы вы использовали
@Table
аннотацию на вашем классе. Хех. :)источник
Даже если ваши аннотации объявлены правильно для правильного управления отношениями «один ко многим», вы все равно можете столкнуться с этим точным исключением. При добавлении нового дочернего объекта
Transaction
в подключенной модели данных вам нужно будет управлять значением первичного ключа - если только вы этого не предполагаете . Если перед вызовом указать значение первичного ключа для дочернего объекта, объявленного следующим образомpersist(T)
, вы столкнетесь с этим исключением.В этом случае аннотации объявляют, что база данных будет управлять генерацией значений первичного ключа сущности при вставке. Предоставление одного (например, через установщик идентификатора) вызывает это исключение.
Альтернативно, но фактически то же самое, это объявление аннотации приводит к тому же исключению:
Поэтому не устанавливайте
id
значение в коде приложения, когда оно уже управляется.источник
Если ничего не помогает, и вы все еще получаете это исключение, проверьте свои
equals()
методы - и не включайте в него дочернюю коллекцию. Особенно, если у вас есть глубокая структура встроенных коллекций (например, A содержит B, B содержит C и т. Д.).В примере
Account -> Transactions
:В приведенном выше примере удалить транзакции из
equals()
чеков. Это связано с тем, что hibernate будет означать, что вы не пытаетесь обновить старый объект, но вы передаете новый объект для сохранения при каждом изменении элемента в дочерней коллекции.Конечно , это решение не будет соответствовать всем приложениям , и вы должны тщательно проектировать то , что вы хотите включить в
equals
иhashCode
методах.источник
Возможно, это ошибка OpenJPA, при откате она сбрасывает поле @Version, но pcVersionInit сохраняет значение true. У меня есть AbstraceEntity, который объявил поле @Version. Я могу обойти это путем сброса поля pcVersionInit. Но это не очень хорошая идея. Я думаю, что это не работает, когда у каскада сохраняются сущности.
источник
Вам необходимо установить транзакцию для каждой учетной записи.
Или этого будет достаточно (если необходимо), чтобы установить нулевые идентификаторы на многих сторонах.
источник
источник
Мой ответ на основе Spring Data JPA: я просто добавил
@Transactional
аннотацию к внешнему методу.Почему это работает
Дочерняя сущность немедленно становилась отсоединенной, поскольку не было активного контекста Hibernate Session. Предоставление транзакции Spring (Data JPA) гарантирует наличие сеанса Hibernate.
Ссылка:
https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/
источник
В моем случае я совершал транзакцию, когда использовался постоянный метод. После изменения сохраняются, чтобы сохранить метод, это было решено.
источник
Если вышеуказанные решения не работают, просто один раз прокомментируйте методы getter и setter класса сущности и не устанавливайте значение id. (Первичный ключ). Тогда это будет работать.
источник
@OneToMany (mappedBy = "xxxx", cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE}) работал для меня.
источник
Разрешается сохранение зависимого объекта до следующего.
Это случилось со мной, потому что я не устанавливал Id (который не был сгенерирован автоматически). и пытается сохранить с отношением @ManytoOne
источник