【问题标题】:JPA/Hibernate5: How to replace a child object mapped with @OneToOne and @MapsId?JPA/Hibernate5:如何替换用@OneToOne 和@MapsId 映射的子对象?
【发布时间】:2020-02-15 05:56:24
【问题描述】:

我在父实体和子实体之间有一个双向@OneToOne 关联,@MapsId 共享主键。

使用 Parent 对象,我可以添加一个 Child,删除它,然后添加一个新的 Child 对象:

        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);

但是,有没有办法设置一个新的 Child 对象而不删除(和刷新)旧的?

如果我从中间块中删除flush(),Hibernate 会抱怨标识符已经与会话相关联。我想我正在寻找一种方法来告诉 Hibernate 忽略旧的 Child 实体,允许新的具有相同的 id,并为新的 Child 运行 SQL 更新而不是插入。

我意识到我可以只更新当前的子字段,但我想知道是否可以进行完整的对象替换。

显示实体定义的完整代码在这里:

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.1 1.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

【问题讨论】:

  • 我没有你的 Hibernate 版本,但我建议你试试manager.remove(child); 而不是child.parent = null;。也许您必须在刷新后添加manager.clear();
  • 感谢您的建议。如果我只使用remove(child),即使我取消关联,我也会得到deleted object would be re-saved by cascade (remove deleted object from associations)。如果我也detach(child),Hibernate 会尝试插入并且我得到唯一约束违规。
  • 你能显示已创建的 DDL(创建表,包括键、外键等)吗?
  • SQL 生成和最终表状态现在添加到问题中
  • 我能想到的唯一其他方法是 detach 来自经理的实体,使用 createNativeQueryrefresh 删除和插入子实体 - 但随后使用 flush 更容易,那为什么不坚持呢?顺便说一句:生成的 DDL 看起来与使用 Joined table strategy 的继承映射完全一样,并且一定不能删除 Child。

标签: java hibernate jpa


【解决方案1】:

我发现解决此问题的一种方法是创建所需的新Child 对象,使用此新Child 对象正确引用Parent,然后将新Child 对象的ID 设置为父对象,最后使用EntityManager 合并Child 对象。

以下是 Groovy 代码,因此 null 检查和语法与 Java 略有不同。这是未经测试的代码,但与我在类似情况下使用的代码相同。

//load the Parent class
Parent _parent = entityManager.find(Parent.class, 1L)

//create a new Child object, passing in ref to parent
Child child = new Child()
child.parent = _parent
//check if child already exists with _parent
if(!_parent.child){
    //no child is currently associated, so associate and persist
    _parent.child = child
    //persist the child, which owns the relationship
    entityManager.persist(child)
}else{
    //update existing, set the id since they are OneToOne
    child.id = _parent.id
    _parent.child = child
    //merge the child
    entityManager.merge(_parent.child)
}

【讨论】:

    猜你喜欢
    • 2014-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-05
    • 1970-01-01
    • 2021-12-25
    • 2013-01-16
    相关资源
    最近更新 更多