java 使用 orphanRemoval 触发 Hibernate 违反约束
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 
原文地址: http://stackoverflow.com/questions/3068817/
Warning: these are provided under cc-by-sa 4.0 license.  You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Hibernate triggering constraint violations using orphanRemoval
提问by ptomli
I'm having trouble with a JPA/Hibernate (3.5.3) setup, where I have an entity, an "Account" class, which has a list of child entities, "Contact" instances. I'm trying to be able to add/remove instances of Contact into a List<Contact> property of Account.
我在使用 JPA/Hibernate (3.5.3) 设置时遇到了问题,我有一个实体,一个“帐户”类,它有一个子实体列表,“联系人”实例。我正在尝试能够将 Contact 的实例添加/删除到 Account 的 List<Contact> 属性中。
Adding a new instance into the set and calling saveOrUpdate(account) persists everything lovely. If I then choose to remove the contact from the list and again call saveOrUpdate, the SQL Hibernate seems to produce involves setting the account_id column to null, which violates a database constraint.
将一个新实例添加到集合中并调用 saveOrUpdate(account) 可以保留所有可爱的东西。如果我然后选择从列表中删除联系人并再次调用 saveOrUpdate,则 SQL Hibernate 似乎产生涉及将 account_id 列设置为 null,这违反了数据库约束。
What am I doing wrong?
我究竟做错了什么?
The code below is clearly a simplified abstract but I think it covers the problem as I'm seeing the same results in different code, which really is about this simple.
下面的代码显然是一个简化的摘要,但我认为它涵盖了问题,因为我在不同的代码中看到了相同的结果,这真的很简单。
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);
Result:
结果:
UPDATE contact SET account_id = null WHERE contact_id = ?
Edit #1:
编辑#1:
It might be that this is actually a bug http://opensource.atlassian.com/projects/hibernate/browse/HHH-5091
可能这实际上是一个错误 http://opensource.atlassian.com/projects/hibernate/browse/HHH-5091
Edit #2:
编辑#2:
I've got a solution that seems to work, but involves using the Hibernate API
我有一个似乎有效的解决方案,但涉及使用 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;
}
Since Hibernate CascadeType.DELETE_ORPHAN is deprecated, I'm having to assume that it has been superseded by the JPA2 version, but the implementation is lacking something.
由于 Hibernate CascadeType.DELETE_ORPHAN 已被弃用,我不得不假设它已被 JPA2 版本取代,但实现中缺少一些东西。
回答by Pascal Thivent
Some remarks:
一些备注:
- Since you have a bi-directional association, you need to add a 
mappedByattribute to declare the owning side of the association. - Also don't forget that you need to manage both sides of the link when working with bi-directional associations and I suggest to use defensive methods for this (shown below).
 - And you must implement 
equalsandhashCodeonContact. 
- 由于您有一个双向关联,您需要添加一个
mappedBy属性来声明关联的拥有方。 - 另外不要忘记在使用双向关联时您需要管理链接的两端,我建议为此使用防御方法(如下所示)。
 - 你必须实现
equals与hashCode上Contact。 
So, in Account, modify the mapping like this:
因此,在 中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
}
In Contact, the important part is that the @ManyToOnefield should have the optionalflag set to false:
在 中Contact,重要的部分是该@ManyToOne字段应将optional标志设置为false:
@Entity
public class Contact {
    @Id @GeneratedValue
    public Long id;
    @ManyToOne(optional = false)
    public Account account;
    // getters, setters, equals, hashCode
}
With these modifications, the following just works:
通过这些修改,以下只是有效:
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());
And the orphaned Contactgets deleted, as expected. Tested with Hibernate 3.5.3-Final.
Contact正如预期的那样,孤儿被删除了。使用 Hibernate 3.5.3-Final 测试。

