Hibernate triggering constraint violations using orphanRemoval

15,757

Some remarks:

  • Since you have a bi-directional association, you need to add a mappedBy attribute 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 equals and hashCode on Contact.

So, in Account, modify the mapping like this:

@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 @ManyToOne field should have the optional flag set to 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 Contact gets deleted, as expected. Tested with Hibernate 3.5.3-Final.

Share:
15,757
ptomli
Author by

ptomli

Updated on June 03, 2022

Comments

  • ptomli
    ptomli almost 2 years

    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.

    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.

    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:

    It might be that this is actually a bug http://opensource.atlassian.com/projects/hibernate/browse/HHH-5091

    Edit #2:

    I've got a solution that seems to work, but involves using the 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.

  • ptomli
    ptomli almost 14 years
    It seems that somewhere in the changes I made to get Hibernate specific API working, I solved whatever it was that was breaking the JPA2 version. Somewhat embarrassingly I suspect it was the equals/hashCode...
  • Shantaram Tupe
    Shantaram Tupe about 6 years
    hello sir, I'm not using EntityManager, I'm using Session#saveOrUpdate hibernate specific, In DB I've 3 children, and from view I've 2 children to be updated and want to remove 3rd or last one, How to deal with that situation? Should I use Session#get() or Session#load(), to know which element to remove?