#java #hibernate #jpa
Вопрос:
У меня есть следующее сопоставление сущностей (однонаправленное):
@Entity
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinColumn(name = "ch_id", referencedColumnName = "ch_id", nullable = false, updatable = false)
private Child child;
}
Дочерняя сущность-это просто обычная сущность.
Я хочу создать родительскую сущность с дочерней, но у меня есть два варианта использования:
- Когда дочерняя сущность уже существует в базе данных
- Когда дочерняя сущность еще не создана в базе данных
когда я перехожу ко второму варианту использования, я успешно создаю дочернюю сущность с родительской сущностью (с помощью сохранения каскадного типа).
Но когда я просматриваю первый вариант использования, я получаю
org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.example.data.entity.Child; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.example.data.entity.Child
org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:319)
org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255)
org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528)
org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:178)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
jdk.proxy2/jdk.proxy2.$Proxy240.saveAll(Unknown Source)
Как я могу пропустить эту ошибку, когда мне просто нужно связать родителя с ребенком, не удаляя тип каскада
В ответ на комментарий @Daniel Wosch:
Здесь у меня есть родительская сущность
public class ParentBO {
private Long childId;
// other fields
}
и это поле childID является необязательным
итак, я получаю список этого родительского ЛС
public void createParents(Collection<ParentBO> creationBOs) {
List<Parent> parentsToPersistWithoutChild = creationBOs.stream()
.map(parent -> prepareParentToPersist(parent))
.collect(toList());
repository.saveAll(parentsToPersist);
}
private Parent prepareParentToPersist(ParentBO parentBO) {
Parent parent = new Parent();
if (parentBO.getChildId != null) {
// here I want just link child entity which already exists in DB with parent entity
// (child existence I've validated before)
parent.setChild(new Child(parentBO.getChildId()));
} else {
Child child = new Child();
// here I set all required fields for child entity which
// depends on parent fields (by business rules)
parent.setChild(child);
}
return parent;
}
Комментарии:
1. Как выглядит ваш код для создания обоих вариантов использования? Особенно первый, который не работает. Вы сначала извлекаете ребенка из базы данных, а затем назначаете его родителю?
2. У меня есть список родительских сущностей, которые мне нужно создать с дочерними сущностями для каждого родителя. И дочерняя сущность может быть или не быть уже сохранена в БД
3. Я стараюсь не сначала извлекать, а затем ссылаться на родительское сохранение, чтобы улучшить читаемость кода
4. Не могли бы вы опубликовать этот код, пожалуйста? Как вы связываете ребенка <-> с родителем?
new Child()
без сохранения-это, по сути, обособленная сущность.5. Я привел пример псевдокода выше, о котором идет речь
Ответ №1:
В первом случае вам нужно сначала извлечь ребенка из базы данных, чтобы поместить его в хранилище сеансов гибернации и управлять им с помощью hibernate.
new Child()
=> переходная сущность. Затем вы неявно устанавливаете идентификатор нового дочернего элемента. Hibernate предполагает, что это управляемый объект (потому что, если у него есть идентификатор, он должен находиться в моем состоянии). Но в вашем коде это не так. Таким образом, сохранение сущности приводит к разнесенному исключению.Отсоединение означает, что это объект, который сохранялся в прошлом и больше не поддерживается / не управляется hibernate. Это происходит потому, что вы вручную задаете идентификатор этой сущности после создания нового экземпляра. Вместо создания нового экземпляра извлеките дочерний экземпляр из базы данных и установите его в родительский.
- Сначала извлеките дочернюю сущность (теперь она управляется hibernate)
- Установите дочернюю сущность в родительскую сущность
- Сохраните родителя
- Будьте счастливы 😀
Комментарии:
1. Спасибо за ваш ответ! Но есть ли шанс сделать это с помощью new Child(), потому что у меня есть две разные службы для работы с дочерними и родительскими объектами, и когда я получаю дочерние сущности, я получаю объекты BO вместо объекта сущности, поэтому я не могу явно задать дочернюю сущность ?
2. Поскольку у меня нет опыта в этом, но вы могли бы попробовать вызвать EntityManager.merge(дочерний). Это добавит отделенный объект в постоянный контекст, и теперь им управляет Hibernate. Важно , что вам нужно установить идентификатор перед вызовом merge. Далее вам необходимо использовать возвращенную сущность из слияния для дальнейшей обработки (установить значение родительский). Не работайте с сущностью, которую вы предоставили функции слияния. Вам необходимо внедрить экземпляр EntityManager в свой сервис, чтобы работать с ним.
3. Попробуйте и дайте мне знать, сработало ли это.