2010-06-18 6 views
15

Tengo problemas con la configuración de JPA/Hibernate (3.5.3), donde tengo una entidad, una clase "Cuenta", que tiene una lista de entidades secundarias, instancias de "Contacto" . Estoy tratando de poder agregar/eliminar instancias de contacto en una lista <Contacto> propiedad de la cuenta.Infracciones de restricción de activación de Hibernate usando orphanRemoval

Agregando una nueva instancia en el conjunto y llamando a saveOrUpdate (cuenta) persiste todo encantador. Si luego selecciono eliminar el contacto de la lista y llamo de nuevo a saveOrUpdate, el SQL Hibernate parece producir el ajuste de la columna account_id a null, lo que infringe una restricción de la base de datos.

¿Qué estoy haciendo mal?

El código siguiente es claramente un resumen simplificado, pero creo que cubre el problema ya que estoy viendo los mismos resultados en código diferente, que realmente es así de 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); 

Resultado:

UPDATE contact SET account_id = null WHERE contact_id = ? 

Edición # 1:

Puede ser que esto es en realidad un error http://opensource.atlassian.com/projects/hibernate/browse/HHH-5091

Edición # 2:

tengo una solución que parece funcionar, pero implica el uso de la API de 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<Contact> contacts = new HashSet<Contact>(); 
} 

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

Desde Hibernate CascadeType.DELETE_ORPHAN está en desuso, estoy teniendo a Supongamos que ha sido reemplazado por la versión JPA2, pero la implementación le falta algo.

+0

Creo que lo que hizo funcionar la cosa, es la opción nullable = false en @JoinColumn y opcional = falso en @ManyToOne, no la cascada de hibernación personalizada. – Thierry

+0

Incluso cuando agregué las marcas con opción de nulos y opcionales, aún falló, pero esta vez cuando se dio cuenta de que había creado un escenario donde violaba una restricción anotada, en lugar de una SQL. Parece que todavía hay algunos problemas con 3.5.3 y JPA2, ya que no puedo hacer que @ElementCollection actúe bien. – ptomli

+0

establezca CascadeType, ya que ALL realizará todas las opertaciones sin tener que ser explícito como DELETE_ORPHAN –

Respuesta

19

Algunas observaciones:

  • Puesto que usted tiene una asociación bidireccional, es necesario añadir un atributo mappedBy para declarar la parte propietaria de la asociación.
  • Además, no olvide que debe gestionar ambos lados del enlace cuando trabaje con asociaciones bidireccionales y le sugiero que utilice métodos de defensa para esto (que se muestran a continuación).
  • Y debe implementar equals y hashCode en Contact.

Así, en Account, modificar el mapeo:

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

En Contact, la parte importante es que el campo @ManyToOne debe tener la bandera optional establecido en false:

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

    @ManyToOne(optional = false) 
    public Account account; 

    // getters, setters, equals, hashCode 

} 

Con estas modificaciones, lo siguiente simplemente funciona:

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

Y el huérfano Contact se elimina, como se esperaba. Probado con Hibernate 3.5.3-Final.

+0

Parece que en algún lugar de los cambios que realicé para que funcione la API específica de Hibernate, resolví lo que fuera que estuviera rompiendo la versión de JPA2. De un modo vergonzoso, sospecho que fue igual/hashCode ... – ptomli

+0

hola señor, no estoy usando 'EntityManager', estoy usando' Session # saveOrUpdate' hibernate specific, en DB tengo ** 3 hijos **, y por ver, tengo ** 2 hijos ** para actualizar y quiero eliminar ** 3º o último **, ¿cómo manejar esa situación? ¿Debo usar 'Session # get()' o 'Session # load()', para saber qué elemento eliminar? –

Cuestiones relacionadas