JPA / Hibernate5: Как заменить дочерний объект, сопоставленный с @OneToOne и @MapsId? - PullRequest
0 голосов
/ 18 октября 2019

У меня двунаправленная связь @OneToOne между родительскими и дочерними объектами, с @MapsId для совместного использования первичного ключа.

С помощью родительского объекта я могу добавить дочерний объект, удалить его, а затем добавить новый дочерний объект. object:

        Parent parent = new Parent();
        parent.name = "parent";
        Child child = new Child();
        child.name = "child";
        child.parent = parent;
        parent.child = child;
        manager.persist(parent);
        manager.flush();

        // Replace this block with ?
        parent.child = null;
        child.parent = null;
        manager.persist(parent);
        manager.flush();

        Child child2 = new Child();
        child2.name = "child2";
        child2.parent = parent;
        parent.child = child2;
        manager.persist(parent);

Но, есть ли способ установить новый дочерний объект, не удаляя (и не стирая) старый?

Если я удаляю flush() из среднего блока, Hibernate жалуется, что идентификатор уже связан с сеансом. Я думаю, что я ищу способ сказать Hibernate игнорировать старую сущность Child, позволить новой иметь тот же идентификатор и запустить обновление SQL для нового Child вместо insert.

Я понимаю,Я могу просто обновить текущие дочерние поля, но я хотел бы знать, возможна ли полная замена объекта.

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

import javax.persistence.*;

public class OneToOneExample {

    @Entity(name = "Parent")
    public static class Parent {
        @Id
        @GeneratedValue
        Long id;

        @OneToOne(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
        Child child;

        String name;
    }

    @Entity(name = "Child")
    public static class Child {
        @Id
        Long id;

        @OneToOne
        @MapsId
        Parent parent;

        String name;
    }

    private static void runExample(EntityManager manager) {
        Parent parent = new Parent();
        parent.name = "parent";
        Child child = new Child();
        child.name = "child";
        child.parent = parent;
        parent.child = child;
        manager.persist(parent);
        manager.flush();

        // Replace this block with ?
        parent.child = null;
        child.parent = null;
        manager.persist(parent);
        manager.flush();

        Child child2 = new Child();
        child2.name = "child2";
        child2.parent = parent;
        parent.child = child2;
        manager.persist(parent);
    }

    public static void main(String[] args) {
        EntityManagerFactory factory = null;
        EntityManager manager = null;
        try {
            factory = Persistence.createEntityManagerFactory(OneToOneExample.class.getPackageName());
            manager = factory.createEntityManager();
            manager.getTransaction().begin();
            runExample(manager);
            manager.getTransaction().commit();
        } finally {
            if (manager != null) manager.close();
            if (factory != null) factory.close();
        }
    }
}

Исключение:

Exception in thread "main" javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [....OneToOneExample$Child#1]
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:123)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:733)
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:695)
    at org.hibernate.engine.spi.CascadingActions$7.cascade(CascadingActions.java:298)
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:492)
    at ...

Я использую Hibernate 5.4.6.Final, Hibernate-JPA2.11.0.2.Final, HSQLDB 2.5.0

Обновление: исходное поколение Hibernate SQL и данные таблицы:

Hibernate: drop table if exists Child CASCADE 
Hibernate: drop table if exists Parent CASCADE 
Hibernate: drop sequence hibernate_sequence if exists
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: create table Child (name varchar(255), parent_id bigint not null, primary key (parent_id))
Hibernate: create table Parent (id bigint not null, name varchar(255), primary key (id))
Hibernate: alter table Child add constraint FKlh67j1n7x7gt59u0pbkwqh6o6 foreign key (parent_id) references Parent
Hibernate: call next value for hibernate_sequence
Hibernate: insert into Parent (name, id) values (?, ?)
Hibernate: insert into Child (name, parent_id) values (?, ?)
Hibernate: delete from Child where parent_id=?
Hibernate: insert into Child (name, parent_id) values (?, ?)

SELECT * FROM CHILD
    NAME, PARENT_ID
    child2, 1
SELECT * FROM PARENT
    ID, NAME
    1, parent

...