Hibernate che innesca le violazioni dei vincoli usando orphanRemoval

Ho problemi con l’installazione di JPA / Hibernate (3.5.3), dove ho un’ quadro, una class “Account”, che ha un elenco di quadro figlio, istanze di “Contatto”. Sto cercando di essere in grado di aggiungere / rimuovere le istanze di Contatto in una proprietà List di Account.

L’aggiunta di una nuova istanza nel set e la chiamata saveOrUpdate (account) persiste in tutto ciò che è bello. Se poi scelgo di rimuovere il contatto dall’elenco e di nuovo chiamare saveOrUpdate, l’Hibernate SQL sembra produrre implica l’impostazione della colonna account_id su null, che viola un vincolo del database.

Che cosa sto facendo di sbagliato?

Il codice sotto è chiaramente un abstract semplificato ma penso che copra il problema visto che sto vedendo gli stessi risultati in un codice diverso, che riguarda davvero questo semplice.

SQL:

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

Giava:

 @Entity class Account { @Id @Column public Long id; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "account_id") public List 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); 

Risultato:

 UPDATE contact SET account_id = null WHERE contact_id = ? 

Modifica n. 1:

Potrebbe essere che questo sia in realtà un bug http://opensource.atlassian.com/projects/hibernate/browse/HHH-5091

Modifica n. 2:

Ho una soluzione che sembra funzionare, ma comporta l’utilizzo dell’API di Hibernate

 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 contacts = new HashSet(); } class Contact { @ManyToOne(optional = false) @JoinColumn(name = "account_id", nullable = false) private Account account; } 

Dato che Hibernate CascadeType.DELETE_ORPHAN è deprecato, devo supporre che sia stato sostituito dalla versione JPA2, ma l’implementazione manca di qualcosa.

Alcune osservazioni:

  • Poiché si dispone di un’associazione bidirezionale, è necessario aggiungere un attributo mappedBy per dichiarare il lato proprietario dell’associazione.
  • Inoltre, non dimenticare che devi gestire entrambi i lati del link quando lavori con associazioni bidirezionali e ti suggerisco di usare metodi difensivi per questo (vedi sotto).
  • E devi implementare equals e hashCode su Contact .

Quindi, in Account , modifica la mapping in questo modo:

 @Entity public class Account { @Id @GeneratedValue public Long id; @OneToMany(cascade = CascadeType.ALL, mappedBy = "account", orphanRemoval = true) public List contacts = new ArrayList(); 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 , la parte importante è che il campo @ManyToOne deve avere il flag optional impostato su false :

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

Con queste modifiche, il seguente funziona:

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

E il Contact orfano viene eliminato, come previsto. Testato con Hibernate 3.5.3-Final.