2011-11-27 35 views
8

Tengo un entidad padre con un entidad Niño en un relación ManyToOne:¿Cómo evitar duplicados con cascadas JPA?

@Entity class Parent { 
    // ... 
    @ManyToOne((cascade = {CascadeType.ALL}) 
    private Child child; 
    // ... 
} 

El Niño tiene un campo único:

@Entity class Child { 
    // ... 
    @Column(unique = true) 
    private String name; 
    // ... 
} 

Cuando necesito un nuevo Niño, pregunto al ChildDAO abeto ST:

Child child = childDao.findByName(name); 
if(child == null) { 
    child = new Child(name); 
} 

Parent parent = new Parent(); 
parent.setChild(child); 

El problema es que si me gusta por encima de dos veces (con el mismo nombre para el Niño), y sólo persiste la Padres al final, me sale una excepción de restricción. Lo cual parece normal, ya que inicialmente no había ningún niño en la base de datos con el nombre especificado.

El problema es que no estoy seguro de cuál sería la mejor manera de evitar esta situación.

+2

¿Estás seguro de que tu modelo está bien? El niño puede tener muchos padres y el padre solo puede tener un hijo. –

+0

Mi respuesta asume una relación OneToOne: el comentario anterior es absolutamente correcto, es un nombre extraño si es correcto. –

+0

Sí, el modelo es correcto. Cambié el nombre de las entidades por el ejemplo, y parece que podría haberlo hecho mejor. – Cos64

Respuesta

9

Está creando dos instancias no persistentes de Child con new Child() dos veces y las coloca en dos Padres diferentes. Cuando persista en los objetos principales, cada una de las dos nuevas instancias secundarias se mantendrá/insertará en cascada, cada una con un @Id diferente. La restricción única en el nombre se rompe. Si está haciendo CascadeType.ALL, entonces cada vez que lo haga new Child() puede obtener un objeto persistente por separado.

Si realmente deseaba que las dos instancias Child se trataran como un solo objeto persistente con la misma ID, tendría que persistir por separado para asociarlo con el contexto/sesión de persistencia. Las llamadas posteriores al childDao.findByName enjuagarán la inserción y devolverán el nuevo elemento secundario que acaba de crear, por lo que no estará haciendo new Child() dos veces.

1

Está configurando un objeto hijo y si persiste el padre está almacenando un registro en la base de datos que apunta a un elemento secundario no existente.

Cuando crea un nuevo objeto, debe ser gestionado por el entityManager de la misma manera cuando "encuentra" un objeto utilizando el DAO debe obtener el registro de DB y colocar el objeto en el contexto de entityManager.

Intente primero persistir o combinar el objeto secundario.

+0

El campo secundario de Parent's tiene establecido 'CascadeType.ALL'. No se requiere fusión explícita. –

6

Lo obtiene porque intenta persistir un objeto que ya existe (la misma ID). Su cascada probablemente no persista es MERGE/UPDATE/REMOVE/DETACH. La mejor forma de evitar esta situación es configurar la cascada correctamente o administrar las cascadas manualmente. Básicamente hablando, la cascada persiste es el culpable habitual.

Es probable quieren algo como esto:

//SomeService--I assume you create ID's on persist 
saveChild(Parent parent, Child child) 
{ 
    //Adding manually (using your cascade ALL) 
    if(child.getId() == null) //I don't exist persist 
    persistence.persist(child); 
    else 
    persistence.merge(child); //I'm already in the DB, don't recreate me 

    parent.setChild(child); 
    saveParent(parent); //using a similar "don't duplicate me" approach. 
} 

Las cascadas pueden ser extremadamente frustrante si usted deja el marco manejarlo, ya que tendrá en ocasiones se pierde actualizaciones si es el almacenamiento en caché (en particular con colecciones en ManyToOne relaciones). Si no guarda explícitamente el elemento primario y permite que el marco se ocupe de las cascadas. En términos generales, concedo Cascade.ALL a mis padres en una relación y cascada de DELETE/PERSIST de mis hijos. Soy un usuario de Eclipselink, por lo que mi experiencia con Hibernate/other es limitada y no puedo hablar con el almacenamiento en caché. * Realmente, piénselo: si está guardando un niño, ¿realmente desea guardar todos los objetos relacionados con él? Además, si está agregando un niño, ¿no debería notificar a los padres? *

En su caso, simplemente tendría un método "saveParent" y "saveChild" en mi Dao/Servicio que garantizara que el caché sea correcto y la base de datos sea correcta para evitar el dolor de cabeza. Administrar manualmente significa que tendrá control absoluto y no tendrá que depender de las cascadas para hacer su trabajo sucio. En una sola dirección, son fantásticos, en el momento en que lleguen "aguas arriba" vas a tener comportamientos inesperados.

3

Si crea dos objetos y deja que JPA los mantenga automáticamente, obtendrá dos filas en la base de datos. Entonces, tienes dos opciones: no hagas dos objetos, o no dejes que JPA los persista automáticamente.

Para evitar hacer dos objetos, debería organizar su código para que si dos padres intentan crear niños con el mismo nombre, obtendrán la misma instancia real. Es probable que desee un WeakHashMap (con el alcance de la solicitud actual, tal vez al referirse a las variables locales), con la tecla de su nombre, donde los padres pueden buscar el nombre de su nuevo hijo para ver si el niño ya existe. Si no, pueden crear el objeto y ponerlo en el mapa.

Para evitar que el JPA persista automáticamente en los objetos, suelte la cascada y use persist para agregar manualmente los objetos al contexto inmediatamente después de la creación.

Dado que el contexto de persistencia es básicamente un WeakHashMap descodificado adjunto a una base de datos, estos enfoques son bastante similares a la hora de realizarlo.

Cuestiones relacionadas