Исправьте обособленную сущность, переданную для сохранения в отношениях один к одному в JPA

#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;
}
 

Дочерняя сущность-это просто обычная сущность.

Я хочу создать родительскую сущность с дочерней, но у меня есть два варианта использования:

  1. Когда дочерняя сущность уже существует в базе данных
  2. Когда дочерняя сущность еще не создана в базе данных

когда я перехожу ко второму варианту использования, я успешно создаю дочернюю сущность с родительской сущностью (с помощью сохранения каскадного типа).

Но когда я просматриваю первый вариант использования, я получаю

 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. Это происходит потому, что вы вручную задаете идентификатор этой сущности после создания нового экземпляра. Вместо создания нового экземпляра извлеките дочерний экземпляр из базы данных и установите его в родительский.

  1. Сначала извлеките дочернюю сущность (теперь она управляется hibernate)
  2. Установите дочернюю сущность в родительскую сущность
  3. Сохраните родителя
  4. Будьте счастливы 😀

Комментарии:

1. Спасибо за ваш ответ! Но есть ли шанс сделать это с помощью new Child(), потому что у меня есть две разные службы для работы с дочерними и родительскими объектами, и когда я получаю дочерние сущности, я получаю объекты BO вместо объекта сущности, поэтому я не могу явно задать дочернюю сущность ?

2. Поскольку у меня нет опыта в этом, но вы могли бы попробовать вызвать EntityManager.merge(дочерний). Это добавит отделенный объект в постоянный контекст, и теперь им управляет Hibernate. Важно , что вам нужно установить идентификатор перед вызовом merge. Далее вам необходимо использовать возвращенную сущность из слияния для дальнейшей обработки (установить значение родительский). Не работайте с сущностью, которую вы предоставили функции слияния. Вам необходимо внедрить экземпляр EntityManager в свой сервис, чтобы работать с ним.

3. Попробуйте и дайте мне знать, сработало ли это.