密钥’PRIMARY’的重复条目’string1-string2′

在使用hibernate和jpa通过MySQL数据库的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 addresses; 

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

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

可以在此链接中找到复合主键类的代码。
并且可以在此链接中找到通过Address扩展的BaseEntity类。

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


编辑:

持久化地址的代码是:

 @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 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指示上面的代码在应用程序崩溃之前运行,但我仍然得到相同的错误。

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

首先,有一些事情要清除:

  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. 持久化操作旨在INSERT临时实体,永远不要合并它们或重新附加实体。 这意味着当您调用服务方法时,HL7Address和HL7GeneralCode都是新实体。 如果您已经使用相同的ID保存了HL7GeneralCode,则将获得主键约束违例exception。

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

UPDATE

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

似乎问题是关于use关系的CascadeType.ALL

这是怎么回事 ?

  • 您的数据库中有一个HL7GeneralCode实例。 我们称之为: code1
  • 您创建一个新Addressuse定义use关系:

    theNewAdress.setUse(code1);

  • 你调用savehl7Address(theNewAddress) ,因为地址是新的,你调用了persist问题是级联规则CascadeType.ALL将强制调用persist(code1) ,因为code1已经在db:crash中,因为重复输入。

方案:

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不在db中。

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

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