【问题标题】:Duplicate entry 'string1-string2' for key 'PRIMARY'键“P​​RIMARY”的重复条目“string1-string2”
【发布时间】:2014-10-08 14:51:29
【问题描述】:

在通过 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;

完整的堆栈跟踪可以查看by clicking on this link
Address 实体的完整代码可以在 at this link 找到。

GeneralCode 实体的完整代码可以阅读at this link

复合主键类的代码可以在at this link找到。
而由Address扩展的BaseEntity类可以在at this link找到。

我已经阅读了很多关于此错误消息的帖子。其他帖子的答案无法解决我的错误消息,并且它们通常无法解决我的实体使用复合主键的事实。


编辑:

持久化地址的代码是:

@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 &amp;&amp; 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;

可以读取新的堆栈跟踪by clicking on this link

我该如何解决这个错误?


第三次编辑:

我按照 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 表明上述代码在应用程序崩溃之前正在运行,但我仍然遇到相同的错误。

您可以阅读生成的堆栈跟踪by clicking on this link

【问题讨论】:

  • 你的EntityManager“persist”代码是什么样的?
  • @isim 我刚刚将持久代码作为编辑添加到我上面原始帖子的末尾。这对你有帮助吗?
  • 所有链接都断开了,这个问题虽然因为答案很好而非常有用,但现在对其他人的用处已减少。不使用外部链接的充分理由。

标签: java spring hibernate jpa hibernate-mapping


【解决方案1】:

首先有几点要搞清楚:

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

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

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

    HL7Address addr = new HL7Address();
    HL7GeneralCode code = new HL7GeneralCode();
    ...
    code.getAddresses().add(addr);
    addr.setUse(code); 
    
  4. persist 操作旨在插入瞬态实体,从不合并它们或重新附加实体。这意味着当您调用服务方法时,HL7Address 和 HL7GeneralCode 都是新实体。如果你已经保存了一个相同ID的HL7GeneralCode,你会得到主键约束违规异常。

  5. 如果 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 列表中添加/删除地址。如果您以后改变主意,请确保您implement equals and hashCode properly

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

【讨论】:

  • +1 谢谢。我尝试了我对您的想法的理解,但它仍然无法解决错误,我有一些问题。对于您的想法 1,我从 address 中删除了 insertable=falseupdatable=false。仍然在想法 1 中,您是说将 Address.use 更改为 OneToManymappedBy?并将GeneralCode.addresses 更改为ManyToOne?对于您的想法 2,我仍然没有在任何一方设置任何 cascade 规则,可以吗?在您的想法 4 中,我的 savehl7Address() 方法具体应该是什么样的?在您的想法5中,如果检索到的代码不为空,为什么要创建一个空代码?
  • 1.如果反过来是原始的关联逻辑,为什么将地址设置为一侧,将代码设置为多。在 1. 我告诉你在很多方面都有 insertable/updatable=true 。如果一个代码有多个地址,则不应更改关联类型。 2. 最好先不用级联,如果证明可以简化一些步骤,以后再添加。 5.我不创建空代码。我尝试先从数据库中获取它。我假设您有时可能会有多个地址共享相同的代码。 4. 检查我更新的答案。
  • 谢谢。这似乎现在可以工作,但我将使用它一段时间以确保它在我将其标记为已回答之前继续工作。由于地址在不可修改的集合中,它抛出了一些错误,但我能够解决这些错误。
  • 您是否将 code.addresses 替换为 Collection.unmodifiableList 或 Arrays.toList?这些方法返回一个不同于 ArrayList 的 List 实现。检查 code.addresses 类型。临时实体应该是 ArrayList,持久实体应该是 PersistentList。
  • 嗯,很高兴能帮上忙。
【解决方案2】:

看来问题是use 关系上的CascadeType.ALL

发生了什么事?

  • 您的数据库中有一个 HL7GeneralCode persistent 实例。我们称之为:code1
  • 您创建一个新的Address 并使用以下内容定义use 关系:

    theNewAdress.setUse(code1);

  • 您拨打savehl7Address(theNewAddress),由于地址是新地址,您拨打了persist问题在于级联规则CascadeType.ALL 将强制调用persist(code1) 并且因为code1 已经在db 中:由于重复条目而崩溃。

解决方案:

use 关系定义无级联规则:

@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 不在数据库中的用例。

过度简化的解决方案(以便您理解问题):

@Override
public void savehl7Address(HL7Address addr) {
    if(addr.use() != null && addr.use().getId()==null){
        //HL7GeneralCode is not persistent yet
        this.em.persist(addr.use());
        //since there is a cascade ALL on the adresses relationship addr is now persistent
        return;
    }
    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);}
}

如您所见,此解决方案肯定不是最好的,也未准备好生产。真正的解决方案是研究所有用例并相应地调整级联规则(在useadresses 关系上)。

在我看来,最好的办法是确保在您调用savehl7AddressHL7GeneralCode 已经是持久的,因此这样的事情可能是一个更好的解决方案:

@Override
public void savehl7Address(HL7Address addr) {
    if(addr.use() != null && addr.use().getId()==null){
        //HL7GeneralCode is not persistent. We don't support that
        throw new IllegalStateException("Cannot persist an adress using a non persistent HL7GeneralCode");
    }
    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);}
}

【讨论】:

  • 感谢您查看我的问题,但您的建议并不能解决我的问题。从use 关系中删除CascadeType.ALL 会导致Addressnull use 字段一起保存。此外,addr.getUse() 没有 .getId() 属性。当我将addr.getUse().getId() 替换为addr.getUse.getCodePk()addr.getId() 时,address 仍然保留有null use 字段。我在调用savehl7Address(addr) 之前使用了SYSO 来确认有一个有效的use.codePk.getCode()use.codePk.getCodesystem(),即使它们没有添加到数据库中
  • 我在上面的原始帖子中添加了第二个编辑,进一步阐明了我的附加代码问题。这是否有助于您确定我的问题的解决方案?
  • @CodeMed 试试这个测试:if(addr.getUse() != null &amp;&amp; !this.em.contains(addr.getUse())){this.em.persist(addr.use());return;}
  • 感谢您的尝试,但更改后仍然出现相同的错误。我通过添加 SYSO 确认它正在运行您的代码,如下所示:if(addr.getUse() != null &amp;&amp; !this.em.contains(addr.getUse())){System.out.println("about to this.em.persist(addr.getUse());");this.em.persist(addr.getUse());return;}。 SYSO 确认它在应用程序崩溃之前运行您的代码。我将生成的堆栈跟踪作为第三次编辑上传到我上面的原始帖子。它确实会在运行代码之前保存相同的子实体,但这并不重要,因为子实体是预先安装的。
猜你喜欢
  • 2012-04-05
  • 2016-10-30
  • 1970-01-01
  • 2011-10-08
  • 1970-01-01
  • 2015-02-10
  • 2021-08-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多