【问题标题】:EclipseLink/JPA: Merge on detached object tree causes primary key violation in child objectEclipseLink/JPA:分离对象树上的合并导致子对象中的主键冲突
【发布时间】:2018-05-19 04:45:27
【问题描述】:

我对 JPA 和通过 JSON 反序列化从应用程序外部传递的分离实体对象有疑问。

我从 JSON 反序列化对象图(通过 HTTP POST 请求传递),然后尝试从中更新现有实体。

据我了解 JPA,在对象上使用 EntityManager.merge() 应该自动且递归地(取决于 cascade)附加它并对树中所有对象的数据库发出更新。 然而,这显然只适用于 1) 没有子实体的单个实体或 2) 对象图中最顶层的实体。

对于子实体,JPA 将始终发出 INSERT INTO,无论对象是否已经存在,因此会遇到主键冲突。

我做错了什么?

实现的相关部分是这样的:

AdminResource.java:

@Autowire
protected CustomerConfigDAO customerConfigDao; // wrapper for the JPA persistence

@POST
@Consumes(MediaType.APPLICATION_JSON)
public void storeConfigurationData(CustomerConfigEntity config) {
    customerConfigDao.update(config); // calls EntityManager.merge(...)
}

CustomerConfigEntity.java:

@Entity
public class CustomerConfigEntity implements Serializable {

    @Id
    private String customerID;

    @OneToOne(mappedBy = "customerConfigEntity", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private DefaultValues defaultValues;

    /* ... snip ... */

    @PrePersist
    @PreUpdate
    public void fixBackReference() {
        if(this.defaultValues != null)
            this.defaultValues.setCustomerConfigEntity(this);
    }
}

DefaultValues.java:

@Entity
public class DefaultValues implements Serializable {

    @Id
    @OneToOne
    @JoinColumn(name = "customerID", referencedColumnName = "cloudID")
    @JsonIgnore
    private CustomerConfigEntity customerConfigEntity;

    /* ... snip ... */

}

有人建议放弃双向一对一关系,转而采用单向关系,但由于 customerConfigEntity 既是外键又是 DefaultValues 中的主键,删除它需要我引入代理键。我通过引入一个自动递增的键来做到这一点,但结果是每当保存 CustomerConfigEntity 时,JPA 都会在 DEFAULTVALUES 中创建一个新记录。

【问题讨论】:

  • 显示更新方法并尝试向其中添加一些调试语句以显示您正在合并的对象图中的内容。 Merge 有望在这个简单的用例中工作,并且 EclipseLink 的单元测试表明它可以工作。尝试将 EclipseLink 日志记录设置为 Finest,并查看它在执行合并时是否查询您的实体。一个快速的解决方法是在更新调用中对实体执行查找操作,因为无论如何任何 JPA 提供者都必须这样做。

标签: jpa merge eclipselink primary-key entitymanager


【解决方案1】:

我通过消除所有双向关系并用单向关系替换它们来解决问题。

CustomerConfigEntity.java:

public class CustomerConfigEntity implements Serializable {

   // ...

   @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
   @PrimaryKeyJoinColumn(name = "customerId", referencedColumnName = "customerId")
   private DefaultValues defaultValues;

   // ...

}

请注意,此处不得使用mappedBy

DefaultValues.java:

   @Id
   private String customerId;

CustomerConfigEntity 有更多的关系,它们都是@OneToMany 基数。

对于那些,我做了同样的事情,但不得不使用@JoinColumn 而不是@PrimaryKeyJoinColumn 来代替customerId - 尽管customerId 的主键的一部分子实体。但是使用@PrimaryKeyJoinColumn 导致 JPA 创建连接表,这不是我想要的。

但是,如果我真的需要双向关系,我仍然不知道如何解决这个问题。

【讨论】:

    猜你喜欢
    • 2012-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-20
    • 2023-03-07
    • 1970-01-01
    相关资源
    最近更新 更多