Está en la página 1de 12

OneToOne, OneToMany, ManyToOne y

ManyToMany

1. Introducción
Java Persistence API, más conocida por sus siglas JPA, es la API de
persistencia desarrollada para la plataforma Java EE . La cual es un
framework del lenguaje de programación Java, que maneja datos
relacionales en aplicaciones usando la plataforma Java en sus ediciones
Standard (Java SE) y Enterprise (Java EE) .

Por su parte, Hibernate es una herramienta de Mapeo objeto-relacional


(ORM), para la plataforma Java – también disponible para .Net, bajo el
nombre de NHibernate – que facilita el mapeo de atributos entre una base de
datos relacional tradicional y el modelo de objetos de una aplicación;
mediante archivos declarativos (XML) o anotaciones en los beans de las
entidades que permiten establecer estas relaciones . Este es software libre,
distribuido bajo los términos de la licencia GNU LGPL.

Es importante destacar que JPA es una parte de la especificación de EJB 3,


es decir que no es un framework, sino que es simplemente un documento en
el cual se especifica los principios básicos de gestión de la capa de
persistencia en el mundo de Java EE . En cambio, Hibernate, si se trata de
un framework que gestiona la capa de persistencia a través de ficheros xml o
anotaciones.

A través de las anotaciones que proporciona JPA cuando usamos Hibernate,


podemos gestionar las relaciones entre dos tablas como si de objetos se
tratasen. Esto facilita el mapeo de atributos de base de datos con el modelo
de objetos de la aplicación. Dependiendo de la lógica de negocio y cómo
modelemos, se podrán crear relaciones unidireccionales o bidireccionales.
2. Relaciones
2.1 @OneToOne (bidireccional)
La siguiente tabla muestra nuestro modelo de base de datos. student_id es
la Foreign Key (a partir de ahora FK) que apunta a student.

Lo primero que deberíamos hacer es preguntarnos quién es el propietario de


la relación, ya que esto determinará dónde irá la respectiva FK. Un estudiante
tiene asociada una matrícula y esa matrícula está asociada a un único
estudiante.

Una buena práctica es usar cascade en la entidad padre ya que nos permite
propagar los cambios y aplicarlos a los hijos. En nuestro ejemplo, tuition no
tiene sentido que exista si student no existe, por lo que student es el que
tendrá el rol padre.

Si observamos la imagen anterior, he decidido que la FK la tenga tuition.


Podemos decir que tuition es el propietario de la relación o propietario de esa
FK (owning side) y student, la no propietaria de la relación, no posee esa FK
(non-owning side). Pero, ¿Cómo creo una relación bidireccional en caso de
que student quiera obtener las propiedades de tuition? Podríamos pensar en
tener otra FK en student apuntando a tuition pero esto generaría una
duplicidad innecesaria en nuestro modelo de base de datos. Para poder
realizar este mapeo correctamente, entran en juego las
anotaciones @JoinColumn y mappedBy.
1 @Entity
2 @Table(name = "student")
3 public class Student {
4
5 @Id
6 @GeneratedValue(strategy = GenerationType.IDENTITY)
7 private Long id;
8
9 private String name;
10
11 @OneToOne(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true, fetch =
12FetchType.LAZY)
13 private Tuition tuition;
14
15 /* Getters and setters */
}

1 @Entity
2 @Table(name = "tuition")
3 public class Tuition {
4
5 @Id
6 @GeneratedValue(strategy = GenerationType.IDENTITY)
7 private Long id;
8
9 private Double fee;
10
11 // Que columna en la tabla Tuition tiene la FK
12 @JoinColumn(name = "student_id")
13 @OneToOne(fetch = FetchType.LAZY)
14 private Student student;
15
16 /* Getters and setters */
17}

@JoinColumn nos permite indicar el nombre de la columna a la que


queremos hacer referencia en la tabla tuition.

Con mappedBy, podemos establecer una relación bidireccional ya que a


pesar de tener una única FK, podemos relacionar ambas tablas. Al final, el
objetivo de las anotaciones es dejar claro donde está la clave que mapea las
relaciones.

orphanRemoval= true especifica que la entidad hijo debe ser eliminada


automáticamente por el propio ORM si ha dejado de ser referenciada por una
entidad padre. p.ej., tenemos una colección de items y eliminamos uno, ese
item ha dejado de tener una referencia y será eliminado. Ojo, no confundir
con cascadeType que son operaciones a nivel de base de datos.

fetchType=LAZY, Recupera la entidad solo cuando realmente la


necesitamos. Importante destacar que la sesión debe estar abierta para
poder invocar al Getter correspondiente y recuperar la entidad, ya que
hibernate usa el patrón Proxy (object proxying) . En caso contrario (al cerrar
la sesión), la entidad pasaría de estado persistent a detach y se lanzaría una
excepción LazyInitializationException.

Vamos a crear un test muy simple para comprobar la sentencia sql que se
está ejecutando.

1 @Test
2 @Transactional
3 @Rollback(false)
4 public void check_sql_statement_when_persisting_in_one_to_one_bidirectional() {
5
6 Student student = new Student();
7 student.setName("Jonathan");
8
9 Tuition tuition = new Tuition();
10 tuition.setFee(150);
11 tuition.setStudent(student);
12
13 student.setTuition(tuition);
14
15 entityManager.persist(student);
16 }

El uso de cascade en la entidad padre, hace que al persistir student se


persista también tuition.

Una alternativa en el ejemplo que acabamos de ver es usar @MapsId. Como


especifiqué anteriormente, matrícula no tiene sentido que exista si estudiante
no existe, solo puede haber asociada una matrícula por estudiante.
Con @MapsId estamos especificando a Hibernate que student_id es PK
(Primary Key) de tuition pero también es FK de student. Ambas entidades
compartirán el mismo valor de identificación y no nos haría
falta @GeneratedValue para la generación de nuevos ids en tuition.

1 @Entity
2 @Table(name = "tuition")
3 public class Tuition {
4
5 @Id
6 private Long id;
7
8 private Double fee;
9
10 @MapsId
11 @OneToOne(fetch = FetchType.LAZY)
12 private Student student;
13
14 /* Getters and setters */
15}

2.2 @OneToMany (bidireccional)


La siguiente tabla muestra nuestro modelo de base de datos. university_id es
la FK que apunta a university.

Normalmente el owning side en este tipo de relaciones suele estar en


el @ManyToOne y el mappedBy iría en la entidad padre.
1 @Entity
2 @Table(name = "university")
3 public class University {
4
5 @Id
6 @GeneratedValue(strategy = GenerationType.IDENTITY)
7 private Long id;
8
9 private String name;
10
11 @OneToMany(mappedBy = "university", cascade = CascadeType.ALL, orphanRemoval = true)
12 private List<Student> students;
13
14 /* Getters and setters */

1 @Entity
2 @Table(name = "students")
3 public class Student {
4
5 @Id
6 @GeneratedValue(strategy = GenerationType.IDENTITY)
7 private Long id;
8
9 private String name;
10
11 @ManyToOne()
12 @JoinColumn(name = "university_id")
13 private University university;
14
15 /* Getters and setters */
16}

2.3 @OneToMany (unidireccional)

En una relación unidireccional @OneToMany, la


anotación @JoinColumn hace referencia a la tabla en base de datos del
many (student en este caso). Por este motivo, vemos en la siguiente
imagen @JoinColumn en la clase University. La clase Student únicamente
tendrá los atributos id y name.
1 @Entity
2 @Table(name = "university")
3 public class University {
4
5 @Id
6 @GeneratedValue(strategy = GenerationType.IDENTITY)
7 private Long id;
8
9 private String name;
10
11 @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
12 @JoinColumn(name = "university_id")
13 private List<Student> students;
14
15 /* Getters and setters */
16}

Vamos a hacer el test y comprobar las sentencias sql que se generan.

1 @Test
2 @Transactional
3 @Rollback(false)
4 public void check_sql_statement_when_persisting_in_one_to_many_unidirectional() {
5 University university = new University();
6 university.setName("Universidad de Las Palmas de Gran Canaria");
7
8 Student student1 = new Student();
9 student1.setName("Ana");
10
11 Student student2 = new Student();
12 student2.setName("Jonathan");
13
14 university.setStudents(List.of(student1, student2));
15
16 entityManager.persist(university);
17 }
¿Por qué se ejecutan los Update?

Al no indicar a student que debe tener una FK mapeando a university (como


hicimos en el ejemplo anterior), Hibernate tiene que ejecutar sentencias
adicionales para resolverlo. Una buena práctica es usar @ManyToOne si
queremos que sea unidireccional o si no crear directamente una relación
bidireccional, de este modo nos ahorraremos la ejecución de queries
innecesarias

2.4 @ManyToMany (bidireccional)


Nuestro modelo de Base de datos es el siguiente
Con @ManyToMany debemos crear una tercera tabla para realizar el mapeo
de ambas entidades. Esta tercera tabla tendrá dos FK apuntando a sus
respectivas tablas padre. Por lo tanto, student_id apunta a la
tabla student y course_id apunta a la tabla course.

1 @Entity
2 @Table(name="course")
3 public class Course {
4
5 @Id
6 @GeneratedValue(strategy = GenerationType.IDENTITY)
7 private Long id;
8
9 private String name;
10
11 private Double fee;
12
13 @ManyToMany(mappedBy = "courses")
14 private Set<Student> students;
15
16 /* Getters and setters */
17}

1 @Entity
2 @Table(name="student")
3 public class Student {
4
5 @Id
6 @GeneratedValue(strategy = GenerationType.IDENTITY)
7 private Long id;
8
9 private String name;
10
11 @ManyToMany(cascade = {
12 CascadeType.PERSIST,
13 CascadeType.MERGE
14 })
15 @JoinTable(
16 name = "student_course",
17 joinColumns = {@JoinColumn(name = "student_id")},
18 inverseJoinColumns = {@JoinColumn(name = "course_id")}
19 )
20 private Set<Course> courses;
21
22 /* Getters and setters */
23}

En este ejemplo el owning side es student y es donde se usa la


anotación @JoinTable. Con ella, especificamos el nombre de la tabla que
realiza el mapeo (student_course). JoinColumns apunta a la tabla del owning
side (student) e InverseJoinColumns apunta a la tabla inversa del owning side
(course). He decidido usar el cascade Merge y Persist, pero no
cascade.Remove ya que si elimino un curso, no quiero que elimine los
estudiantes asociados a él.

Como podemos ver en el ejemplo, estoy usando un Set en vez de List en la


asociación. Esto es porque usando List, Hibernate elimina las filas del objeto
que queremos eliminar y vuelve a insertar las demás. Esto es completamente
innecesario e ineficiente.

La siguiente imagen muestra las sentencias sql generadas usando List. En


este caso, tenemos un estudiante asociado a 4 cursos y queremos eliminar
uno de ellos.

También podría gustarte