mesa de servicio

Ahora veamos otro caso común: muchos a muchos. Imaginemos que tenemos una relación de muchos a muchos entre tareas y empleados :

  • Un empleado en la tabla de empleados puede realizar muchas tareas desde la tabla de tareas.
  • Una tarea en la tabla de tareas se puede asignar a varios empleados.

Esta relación entre entidades se denomina muchos a muchos. Y para implementarlo a nivel de SQL, necesitamos una tabla de servicio adicional. Llamémoslo, por ejemplo, employee_task.

La tabla employee_task contendrá solo dos columnas:

  • ID de empleado
  • ID_tarea

Cada vez que asignemos una tarea específica a un usuario específico, se agregará una nueva fila a esta tabla. Ejemplo:

ID de empleado ID_tarea
1 1
1 2
2 3

Bueno, la tabla de tareas debería perder su columna employee_id . Solo tiene sentido si la tarea solo se puede asignar a un empleado. Si la tarea se puede asignar a varios empleados, esta información debe almacenarse en la tabla de servicio employee_task .

Relación a nivel de tabla

Así es como se verán nuestras nuevas tablas:

identificación nombre ocupación salario edad Fecha de Ingreso
1 ivanov ivan Programador 100000 25 2012-06-30
2 petrov petr Programador 80000 23 2013-08-12
3 Sergey Ivanov Ensayador 40000 treinta 2014-01-01
4 Rabinovich Moisha Director 200000 35 2015-05-12
5 Anastasia Kirienko Gerente de oficina 40000 25 2015-10-10
6 Vaska Gato 1000 3 2018-11-11

Tabla de empleados ( no modificada ) :

Esta tabla tiene las siguientes columnas:

  • identificación INT
  • nombre VARCHAR
  • ocupación VARCHAR
  • salario INT
  • edad INT
  • unirse_fecha FECHA

Y así es como se ve la tabla de tareas , se perdió la columna employee_id (marcada en rojo):

identificación empleado_id nombre fecha límite
1 1 Arreglar un error en la interfaz 2022-06-01
2 2 Arreglar un error en el backend 2022-06-15
3 5 comprar cafe 2022-07-01
4 5 comprar cafe 2022-08-01
5 5 comprar cafe 2022-09-01
6 (NULO) limpiar la oficina (NULO)
7 4 Disfruta la vida (NULO)
8 6 Disfruta la vida (NULO)

Esta tabla ahora tiene solo 3 columnas:

  • id - número de tarea único (y filas en la tabla)
  • empleado_id - (eliminado)
  • nombre - el nombre y la descripción de la tarea
  • fecha límite - el tiempo hasta el cual la tarea debe ser completada

También tenemos la tabla de servicio employee_task , donde los datos de employee_id han migrado de la tabla de tareas:

ID de empleado ID_tarea
1 1
2 2
5 3
5 4
5 5
(NULO) 6
4 7
6 8

Deliberadamente guardé temporalmente la columna eliminada en la tabla de tareas para que pueda ver que los datos se han movido a la tabla employee_task.

Otro punto importante es la línea roja "(NULL) 6" en la tabla employee_task. Lo he marcado en rojo porque no estará en la tabla employee_task .

Si la tarea 7 se asigna al usuario 4, entonces debería haber una fila (4, 7) en la tabla employee_task.

Si la tarea 6 no está asignada a nadie, simplemente no habrá registro para ella en la tabla employee_task. Así es como se verán las versiones finales de estas tablas:

tabla de tareas :

identificación nombre fecha límite
1 Arreglar un error en la interfaz 2022-06-01
2 Arreglar un error en el backend 2022-06-15
3 comprar cafe 2022-07-01
4 comprar cafe 2022-08-01
5 comprar cafe 2022-09-01
6 limpiar la oficina (NULO)
7 Disfruta la vida (NULO)
8 Disfruta la vida (NULO)

tabla empleado_tarea:

ID de empleado ID_tarea
1 1
2 2
5 3
5 4
5 5
4 7
6 8

Comunicación a nivel de clase Java

Pero con la comunicación al nivel de las clases de Entidad, tenemos un orden completo. Comencemos con las buenas noticias.

Primero, Hibernate tiene una anotación especial @ManyToMany que le permite describir bien el caso de una relación de tabla de muchos a muchos.

En segundo lugar, todavía nos bastan dos clases de Entidades. No necesitamos una clase para la mesa de servicio.

Así es como se verán nuestras clases. La clase Employee en su forma original:

@Entity
@Table(name="user")
class Employee {
   @Column(name="id")
   public Integer id;

   @Column(name="name")
   public String name;

   @Column(name="occupation")
   public String occupation;

   @Column(name="salary")
   public Integer salary;

   @Column(name="join_date")
   public Date join;
}

Y la clase EmployeeTask en su forma original:

@Entity
@Table(name="task")
class EmployeeTask {
   @Column(name="id")
   public Integer id;

   @Column(name="name")
   public String description;

   @Column(name="deadline")
   public Date deadline;
}

anotación @ManyToMany

Omitiré los campos existentes en los ejemplos, pero agregaré otros nuevos. Así es como se verán. Clase de empleado :

@Entity
@Table(name="employee")
class Employee {
   @Column(name="id")
   public Integer id;

   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name="employee_task",
	       joinColumns=  @JoinColumn(name="employee_id", referencedColumnName="id"),
           inverseJoinColumns= @JoinColumn(name="task_id", referencedColumnName="id") )
   private Set<EmployeeTask> tasks = new HashSet<EmployeeTask>();

}

Y la clase EmployeeTask :

@Entity
@Table(name="task")
class EmployeeTask {
   @Column(name="id")
   public Integer id;

   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name="employee_task",
       	joinColumns=  @JoinColumn(name="task_id", referencedColumnName="id"),
       	inverseJoinColumns= @JoinColumn(name=" employee_id", referencedColumnName="id") )
   private Set<Employee> employees = new HashSet<Employee>();

}

Parece que todo es complicado, pero en realidad todo es simple.

Primero, usa la anotación @JoinTable (que no debe confundirse con @JoinColumn), que describe la tabla de servicio employee_task.

En segundo lugar, describe que la columna task_id de la tabla employee_task hace referencia a la columna id de la tabla de tareas.

Tercero, dice que la columna employee_id de la tabla employee_task se refiere a la columna id de la tabla employee.

De hecho, con la ayuda de anotaciones, describimos qué datos están contenidos en la tabla employee_task y cómo Hibernate debería interpretarlos.

Pero ahora podemos agregar (y eliminar) muy fácilmente una tarea a cualquier empleado. Y también agregue cualquier ejecutante a cualquier tarea.

Solicitar ejemplos

Escribamos un par de consultas interesantes para comprender mejor cómo funcionan estos campos ManyToMany. Y funcionan exactamente como se esperaba.

Primero, nuestro antiguo código funcionará sin cambios, ya que el director tenía un campo de tareas antes:

EmployeeTask task1 = new EmployeeTask();
task1.description = "Do Something Important";
session.persist(task1);

EmployeeTask task2 = new EmployeeTask();
task2.description = "Nothing to do";
session.persist(task2);
session.flush();

Employee director = session.find(Employee.class, 4);
director.tasks.add(task1);
director.tasks.add(task2);

session.update(director);
session.flush();

En segundo lugar, si queremos asignar otro ejecutante a alguna tarea, entonces es aún más fácil hacer esto:

Employee director = session.find(Employee.class, 4);
EmployeeTask task = session.find(EmployeeTask.class, 101);
task.employees.add(director);

session.update(task);
session.flush();

¡Importante! Como resultado de la ejecución de esta solicitud, no solo la tarea tendrá un ejecutor-director, sino que también el director tendrá la tarea No. 101.

Primero, el hecho de la relación entre el director y la tarea en la tabla employee_task se almacenará como una cadena: (4,101).

En segundo lugar, los campos marcados con anotaciones @ManyToMany son objetos proxy y, cuando se accede a ellos, siempre se ejecuta una consulta a la base de datos.

Entonces, si agrega una tarea a un empleado y guarda información sobre el empleado en la base de datos, luego de eso, la tarea tendrá un nuevo ejecutor en la lista de ejecutores.