java - onetoone - JPA: pregunta sobre desajuste de impedancia en OneToMany relaciones
relacion onetoone jpa (3)
Las relaciones de entidad en JPA tienen lados propietarios e inversos. Las actualizaciones de la base de datos están determinadas por el estado del lado propietario. En su caso, el Employee
es propietario debido al atributo mappedBy
.
De la especificación JPA 2.0 :
2.9 Relaciones entre entidades
...
Las relaciones pueden ser bidireccionales o unidireccionales. Una relación bidireccional tiene un lado propietario y un lado inverso (no propietario). Una relación unidireccional tiene solo un lado dueño. El lado propietario de una relación determina las actualizaciones de la relación en la base de datos, como se describe en la sección 3.2.4.
Las siguientes reglas se aplican a las relaciones bidireccionales:
- El lado inverso de una relación bidireccional debe referirse a su lado propietario mediante el uso del elemento mappedBy de la anotación OneToOne, OneToMany o ManyToMany. El elemento mappedBy designa la propiedad o el campo en la entidad que es el propietario de la relación.
- El lado de muchas de las relaciones bidireccionales de uno a muchos / muchos a uno debe ser el lado propietario, por lo tanto, el elemento mappedBy no se puede especificar en la anotación ManyToOne.
- Para las relaciones bidireccionales uno-a-uno, el lado propietario corresponde al lado que contiene la clave externa correspondiente.
- Para muchas relaciones bidireccionales, cualquier lado puede ser el lado propietario.
Tengo una pregunta sobre las relaciones JPA-2.0 (proveedor es Hibernate) y su gestión correspondiente en Java. Supongamos que tengo un departamento y una entidad de empleado:
@Entity
public class Department {
...
@OneToMany(mappedBy = "department")
private Set<Employee> employees = new HashSet<Employee>();
...
}
@Entity
public class Employee {
...
@ManyToOne(targetEntity = Department.class)
@JoinColumn
private Department department;
...
}
Ahora sé que tengo que administrar las relaciones de Java yo mismo, como en la siguiente prueba unitaria:
@Transactional
@Test
public void testBoth() {
Department d = new Department();
Employee e = new Employee();
e.setDepartment(d);
d.getEmployees().add(e);
em.persist(d);
em.persist(e);
assertNotNull(em.find(Employee.class, e.getId()).getDepartment());
assertNotNull(em.find(Department.class, d.getId()).getEmployees());
}
Si e.setDepartment(d)
o d.getEmployees().add(e)
las aserciones fallarán. Hasta aquí todo bien. ¿Qué pasa si comprometo la transacción de la base de datos en el medio?
@Test
public void testBoth() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Department d = new Department();
Employee e = new Employee();
e.setDepartment(d);
d.getEmployees().add(e);
em.persist(d);
em.persist(e);
em.getTransaction().commit();
em.close();
em = emf.createEntityManager();
em.getTransaction().begin();
assertNotNull(em.find(Employee.class, e.getId()).getDepartment());
assertNotNull(em.find(Department.class, d.getId()).getEmployees());
em.getTransaction().commit();
em.close();
}
¿Todavía necesito administrar ambos lados de la relación? No, como resulta, no es necesario. Con esta modificación
e.setDepartment(d);
//d.getEmployees().add(e);
las afirmaciones aún tienen éxito. Sin embargo, si solo configuro el otro lado:
//e.setDepartment(d);
d.getEmployees().add(e);
las afirmaciones fallan ¿Por qué? ¿Es porque el empleado es el lado dueño de la relación? ¿Puedo cambiar ese comportamiento anotando de manera diferente? ¿O simplemente es siempre el lado "Uno" de "OneToMany" que determina cuándo se llena el campo de clave externa en la base de datos?
La razón por la cual la segunda prueba en un nuevo contexto de persistencia tiene éxito si solo actualiza el lado propietario en un contexto previo es que el proveedor de persistencia obviamente no puede saber que al persistir, usted no actualizó también el lado inverso. Solo se preocupa por el lado propietario para fines de persistencia. Sin embargo, cuando obtiene objetos persistentes de un proveedor de persistencia, el proveedor establece las asociaciones bidireccionales correctamente en ambos lados (simplemente se supone que también se conservaron correctamente). Sin embargo, como muchos otros ya han señalado, no es responsabilidad del proveedor de persistencia completar las asociaciones bidireccionales recién creadas y siempre debe mantener adecuadamente las asociaciones bidireccionales en su código.
No sé qué intenta demostrar tu prueba, pero el hecho es que debes manejar ambos lados de la asociación cuando trabajas con asociaciones bidireccionales. No hacerlo es incorrecto. Período.
Actualización: Si bien la referencia de especificación mencionada por axtavt es, por supuesto, precisa, insisto, definitivamente debe establecer ambos lados de una asociación bidireccional. No hacerlo es incorrecto y la asociación entre sus entidades en el primer contexto de persistencia se rompe . El libro wiki de JPA lo dice así:
Como con todas las relaciones bidireccionales, es responsabilidad de su modelo de objeto y aplicación mantener la relación en ambas direcciones. No hay magia en JPA, si agrega o quita a un lado de la colección, también debe agregar o quitar desde el otro lado, vea la corrupción del objeto . Técnicamente, la base de datos se actualizará correctamente si solo agrega / elimina desde el lado propietario de la relación, pero luego su modelo de objetos no estará sincronizado, lo que puede causar problemas.
En otras palabras, la única forma correcta y segura de administrar su asociación bidireccional en Java es establecer ambos lados del enlace. Esto generalmente se hace usando métodos de administración de enlaces defensivos, como este:
@Entity
public class Department {
...
@OneToMany(mappedBy = "department")
private Set<Employee> employees = new HashSet<Employee>();
...
public void addToEmployees(Employee employee) {
this.employees.add(employee);
employee.setDepartment(this);
}
}
Repito, no hacerlo es incorrecto. Su prueba solo funciona porque está golpeando la base de datos en un nuevo contexto de persistencia (es decir, una situación muy particular, no la general) pero el código se rompería en muchas otras situaciones.