首页 文章

Hibernate使用orphanRemoval触发约束违规

提问于
浏览
15

我在使用JPA / Hibernate(3.5.3)设置时遇到问题,我有一个实体,一个“帐户”类,它有一个子实体列表,“联系”实例 . 我正在尝试将Account的实例添加/删除到Account的List <Contact>属性中 .

将新实例添加到集合中并调用saveOrUpdate(account)可以保持一切可爱 . 如果我然后选择从列表中删除联系人并再次调用saveOrUpdate,则SQL Hibernate似乎会产生涉及将account_id列设置为null,这违反了数据库约束 .

我究竟做错了什么?

下面的代码显然是一个简化的摘要,但我认为它涵盖了问题,因为我在不同的代码中看到相同的结果,这真的是这个简单 .

SQL:

CREATE TABLE account ( INT account_id );
CREATE TABLE contact ( INT contact_id, INT account_id REFERENCES account (account_id) );

Java的:

@Entity
class Account {
  @Id
  @Column
  public Long id;

  @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
  @JoinColumn(name = "account_id")
  public List<Contact> contacts;
}

@Entity
class Contact {
  @Id
  @Column
  public Long id;

  @ManyToOne(optional = false)
  @JoinColumn(name = "account_id", nullable = false)
  public Account account;
}

Account account = new Account();
Contact contact = new Contact();

account.contacts.add(contact);
saveOrUpdate(account);

// some time later, like another servlet request....

account.contacts.remove(contact);
saveOrUpdate(account);

结果:

UPDATE contact SET account_id = null WHERE contact_id = ?

编辑#1:

可能这实际上是一个错误http://opensource.atlassian.com/projects/hibernate/browse/HHH-5091

编辑#2:

我有一个似乎有效的解决方案,但涉及使用Hibernate API

class Account {
    @SuppressWarnings("deprecation")
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "account")
    @Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
    @JoinColumn(name = "account_id", nullable = false)
    private Set<Contact> contacts = new HashSet<Contact>();
}

class Contact {
    @ManyToOne(optional = false)
    @JoinColumn(name = "account_id", nullable = false)
    private Account account;
}

由于Hibernate CascadeType.DELETE_ORPHAN已被弃用,我不得不假设它已被JPA2版本取代,但实现缺乏某些功能 .

1 回答

  • 19

    一些评论:

    • 由于您具有双向关联,因此需要添加 mappedBy 属性以声明关联的拥有方 .

    • 另外不要忘记在处理双向关联时你需要管理链接的两面,我建议使用防御方法(如下所示) .

    • 您必须在 Contact 上实施 equalshashCode .

    因此,在 Account 中,修改如下映射:

    @Entity
    public class Account {
        @Id @GeneratedValue
        public Long id;
    
        @OneToMany(cascade = CascadeType.ALL, mappedBy = "account", orphanRemoval = true)
        public List<Contact> contacts = new ArrayList<Contact>();
    
        public void addToContacts(Contact contact) {
            this.contacts.add(contact);
            contact.setAccount(this);
        }
    
        public void removeFromContacts(Contact contact) {
            this.contacts.remove(contact);
            contact.setAccount(null);
        }
    
        // getters, setters
    }
    

    Contact 中,重要的是 @ManyToOne 字段应将 optional 标志设置为 false

    @Entity
    public class Contact {
        @Id @GeneratedValue
        public Long id;
    
        @ManyToOne(optional = false)
        public Account account;
    
        // getters, setters, equals, hashCode
    
    }
    

    通过这些修改,以下工作正常:

    Account account = new Account();
    Contact contact = new Contact();
    
    account.addToContact(contact);
    em.persist(account);
    em.flush();
    
    assertNotNull(account.getId());
    assertNotNull(account.getContacts().get(0).getId());
    assertEquals(1, account.getContacts().size());
    
    account.removeFromContact(contact);
    em.merge(account);
    em.flush();
    assertEquals(0, account.getContacts().size());
    

    正如预期的那样,孤立的 Contact 被删除了 . 测试了Hibernate 3.5.3-Final .

相关问题