重复输入' string1-string2'关键' PRIMARY'

时间:2014-08-14 20:10:09

标签: java spring hibernate jpa hibernate-mapping

在使用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<HL7Address> addresses;

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

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

可以找到复合主键类的代码at this link 可以找到BaseEntity扩展的Addressat 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 && 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

2 个答案:

答案 0 :(得分:2)

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

  1. HL7GeneralCode(父级)和HL7Address(子级)之间存在双向关联。如果HL7GeneralCode.addresses是&#34;反向&#34; side(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,则会出现主键约束违例异常。

  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);
    
  6. <强>更新

    1. HL7Address地址不会覆盖equals / hashCode,因此默认对象应用相同的引用检查规则。这将确保我们可以在code.addresses列表中添加/删除地址。如果您以后改变主意,请确保implement equals and hashCode properly

    2. 虽然与您的问题无关,但您可能希望使用getter / setter而不是公开您的字段。这提供了更好的封装,您将避免将setter与公共字段访问混合。

    3. 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)

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

发生了什么?

  • 您的数据库中有一个HL7GeneralCode 持久的实例。我们称之为: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关系上)。

在我看来,最好的办法是确保在调用HL7GeneralCodesavehl7Address已经存在,所以这样的事情可能是更好的解决方案:

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