一尘不染

键“ PRIMARY”的条目“ string1-string2”重复

hibernate

在通过MySQL数据库使用hibernate和jpa的Spring MVC应用程序中,每当我尝试保存包括子实体的父实体时,都会收到有关子实体的以下错误消息:

Duplicate entry 'string1-string2' for key 'PRIMARY'

在这里,string1string2引用子实体的组合主键的两个部分。 如何解决此错误?

在父Address实体中定义实体之间的关系的方法如下:

@ManyToOne(cascade = { CascadeType.ALL }, fetch=FetchType.EAGER)
@JoinColumns({ @JoinColumn(name = "usecode", referencedColumnName = "code", insertable = false, updatable = false),
        @JoinColumn(name = "usecodesystem", referencedColumnName = "codesystem", insertable = false, updatable = false)
})
public HL7GeneralCode use;

这是在GeneralCode子实体中定义关系的方式:

@OneToMany(mappedBy = "use", cascade = {CascadeType.ALL})
private Set<HL7Address> addresses;

单击此链接可以查看完整的堆栈跟踪。可以在此链接上找到实体
的完整代码。 Address

GeneralCode可以在此链接上读取实体的完整代码。

可以在此链接中找到复合主键类的代码。扩展
BaseEntityAddress可以在此链接中找到。

我已经阅读了很多关于此错误消息的信息。其他发布的答案不能解决我的错误消息,并且通常不能解决我的实体使用 复合主键 这一事实。


编辑:

保留地址的代码是:

@Override
public void savehl7Address(HL7Address addr) {
    if ((Integer)addr.getId() == null) {
        System.out.println("[[[[[[[[[[[[ about to persist address ]]]]]]]]]]]]]]]]]]]]");
        this.em.persist(addr);}
    else {
        System.out.println("]]]]]]]]]]]]]]]]]] about to merge address [[[[[[[[[[[[[[[[[[[[[");
        this.em.merge(addr);}
}

第二编辑:

我尝试遵循@
Ben75的建议,但是代码在此行崩溃this.em.persist(addr.getUse());。请注意,他的if子句不适合我的实际对象模型,因此我将下面的if子句更改为if(addr.getUse() != null && addr.getId()==null)。这是我的代码。

@Override
public void savehl7Address(HL7Address addr) {
    if(addr.getUse() != null && addr.getId()==null){
        //this next line prints in the stack trace right before the app crashes
        System.out.println("about to this.em.persist(addr.getUse());");
        //HL7GeneralCode is not persistent yet
        this.em.persist(addr.getUse());
        //since there is a cascade ALL on the adresses relationship addr is now persistent
        return;
    }
    System.out.println("=========================== inside jpahl7patientrespository.savehl7Address(addr)");
    if ((Integer)addr.getId() == null) {
        System.out.println("[[[[[[[[[[[[ about to persist address ]]]]]]]]]]]]]]]]]]]]");
        this.em.persist(addr);}
    else {
        System.out.println("]]]]]]]]]]]]]]]]]] about to merge address [[[[[[[[[[[[[[[[[[[[[");
        this.em.merge(addr);}
}

HL7Address的相关部分现在为:

@ManyToOne(fetch=FetchType.EAGER)
@JoinColumns({ @JoinColumn(name = "usecode", referencedColumnName = "code", insertable = false, updatable = false),
        @JoinColumn(name = "usecodesystem", referencedColumnName = "codesystem", insertable = false, updatable = false)
})
public HL7GeneralCode use;

HL7GeneralCode的相关部分现在为:

@OneToMany(mappedBy = "use")
private Set<HL7Address> addresses;

单击此链接可以读取新的堆栈跟踪。

如何解决此错误?


第三编辑:

我遵循ben75的建议,在保存地址方法中添加了以下代码:

if(addr.getUse() != null && !this.em.contains(addr.getUse())){
    System.out.println("about to this.em.persist(addr.getUse());");
    this.em.persist(addr.getUse());return;
}

不幸的是,尽管堆栈跟踪SYSO表明上述代码在应用崩溃之前就在运行,但我仍然遇到相同的错误。

您可以通过单击此链接来读取生成的堆栈跟踪。


阅读 317

收藏
2020-06-20

共1个答案

一尘不染

首先要清除一些事情:

  1. HL7GeneralCode(父级)和HL7Address(子级)之间存在双向关联。如果HL7GeneralCode.addresses是“反”面(mappedBy),那么为什么拥有方HL7Address.use具有可插入/可更新的false?拥有方应控制此关联,因此您应删除insertable / updatable = false标志。

  2. 从父级到子级级联总是有意义的,而不是相反。但是在您的用例中,您尝试保留子项,并自动也保留父项。这就是为什么CASCADE.ALL在多对一末端没有意义的原因。

  3. 使用双向关联时,必须强制设置双方:

        HL7Address addr = new HL7Address();
    HL7GeneralCode code = new HL7GeneralCode();
    ...
    code.getAddresses().add(addr);
    addr.setUse(code); 
  1. 持久操作旨在插入瞬态实体,而不要合并它们或重新附加实体。这意味着在您调用服务方法时,HL7Address和HL7GeneralCode都是新实体。如果您已经保存了具有相同ID的HL7GeneralCode,则将获得主键约束冲突异常。

  2. 如果HL7GeneralCode可能存在,则应从db获取它。

        HL7GeneralCode code = em.find(HL7GeneralCode, pk);
    HL7Address addr = new HL7Address();
    if(code != null) {
       code = new HL7GeneralCode();
       em.persist(code);    
    }
    code.getAddresses().add(addr);
    addr.setUse(code);            
    em.persist(addr);

更新

  1. HL7Address地址不会覆盖equals / hashCode,因此将应用默认对象相同的引用检查规则。这将确保我们可以从code.addresses列表中添加/删除地址。万一以后改变主意,请确保正确实现equals和hashCode

  2. 尽管与您的问题无关,但您可能希望使用getter / setter而不是将字段公开。这样可以提供更好的封装,并且可以避免将setter与公共领域的访问权混在一起。

savehl7Address方法:

    @Override
    public void savehl7Address(HL7Address addr) {
        HL7GeneralCode code = addr.use();
        if(code != null && code.getId()==null){
        //HL7GeneralCode is not persistent. We don't support that
            throw new IllegalStateException("Cannot persist an adress using a non persistent HL7GeneralCode");
           //In case you'd want to support it
           //code = em.find(HL7GeneralCode, code.getId());
        }
        //Merge the code without any address info        
        //This will ensure we only reattach the code without triggering the address 
        //transitive persistence by reachability
        addr.setUse(null);
        code.getAddresses().remove(addr);
        code = em.merge(code); 

        //Now set the code to the address and vice-versa  
        addr.setUse(code);
        code.getAddresses().add(addr);

        if ((Integer)addr.getId() == null) {
            System.out.println("[[[[[[[[[[[[ about to persist address ]]]]]]]]]]]]]]]]]]]]");
            em.persist(addr);
        }
        else {
            System.out.println("]]]]]]]]]]]]]]]]]] about to merge address [[[[[[[[[[[[[[[[[[[[[");
            addr = em.merge(addr);
        }       
    }

2020-06-20