@ManyToMany

By Carlos Quintana

Estimados lectores, de los mapeos posibles con JPA me quedaba publicar @ManyToMany y todavía me queda por publicar @OneToOne, pero bueno ya saben que mientras tenga un tiempo libre, con mucho gusto haré las publicaciones que sean posibles.

Esta vez les traigo un bonito ejemplo y muy entendible de como se hace un mapeo muchos a muchos con una anotación @ManyToMany. Esta vez les voy a dejar los queries de mysql para que si los pueden probar lo hagan rápidamente con 3 tablas y 2 stored procedure y ejecutar el ejemplo de manera se podría decir rápida. No dejo mi ambiente de desarrollo por que hago uso de Spring Framework e inyección de dependencias ademas de un esquema arquitectónico un poco difícil de levantar ademas de hacer uso de entityManager para las consultas y no el framework de Hibernate, pero si quieren usar Hibernate adelante, ya es el gusto de cada uno.

primero que todo explico rápidamente como esta el mapeo de la base de datos:
3 Tablas las cuales son las siguientes:

1. Empleado
2. Proyecto
3. relacion_proyecto

Donde:

Empleado representa un repositorio para los datos de los empleados de x empresa.

Proyecto representa una lista de proyectos en los cuales una empresa x trabaja.

relacion_empleado es una tabla donde se relaciona un proyecto con un empleado. Aquí es donde se establece la relación muchos a muchos porque un empleado pertenece a un proyecto, podría permanecer a uno o mas proyectos al mismo tiempo o en dado caso que la tabla guarde un historial, un empleado va a estar en N proyectos. A su vez muchos proyectos puede que contengan un mismo empleado.

El script para ejecutar las relaciones anteriores con mysql son las siguientes:

Creación de tablas


create table proyecto(
  cve_proyecto int primary key,
  nombre varchar(100)
  );

create table empleado(
  cve_empleado int primary key,
  nombre varchar(100),
  apellido_paterno varchar(100),
  apellido_materno varchar(100)
);

create table relacion_proyectos(
  cve_proyecto int,
  cve_empleado int
);

alter table relacion_proyectos add constraint
fk_primarias primary key (cve_proyecto,cve_empleado);

alter table relacion_proyectos add foreign key
(cve_proyecto) references proyecto (cve_proyecto);

alter table relacion_proyectos add foreign key
(cve_empleado) references empleado (cve_empleado);

Ya que tengo mis tablas hechas en la base de datos procedo a realizar las entidades de persistencia con Java

Vamos a empezar con la clase Proyecto:


package mx.com.chernanq.entidades;

import java.io.Serializable;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;

@Entity
public class Proyecto implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="cve_proyecto")
    private Integer cveProyecto;

    @Column(name="nombre")
    private String nombre;

    //Cada proyecto tiene una lista de empleados
    //Pero hay mas de un proyecto, así que no puede ser
    //Mapeo @OneToMany de acuerdo a nuestro modelo relacional

    @ManyToMany(mappedBy="listaProyectos")
    private List<Empleado> listaEmpleados;

    public List<Empleado> getListaEmpleados() {
        return listaEmpleados;
    }

    public void setListaEmpleados(List<Empleado> listaEmpleados) {
        this.listaEmpleados = listaEmpleados;
    }

    public Integer getCveProyecto() {
        return cveProyecto;
    }

    public String getNombre() {
        return nombre;
    }

    public void setCveProyecto(Integer cveProyecto) {
        this.cveProyecto = cveProyecto;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

}

Curiosamente no necesitamos hacer un mapeo de 3 tablas, como se darán cuenta, solo requerimos las 2 tablas principales, la tabla donde se establecen las relaciones es totalmente lógica para nuestro modelo de persistencia.

Entidad Empleado:


package mx.com.chernanq.entidades;

import java.io.Serializable;
import java.util.List;
import javax.persistence.*;

@Entity
public class Empleado implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="cve_empleado")
    private Integer cveEmpleado;

    @Column(name="nombre")
    private String nombre;

    @Column(name="apellido_paterno")
    private String apellidoPaterno;

    @Column(name="apellido_materno")
    private String apellidoMaterno;

    /*
    En el siguiente fragmento de código se hace la magia del mapeo
    muchos a muchos. Con la anotación @JoinTable le indicamos a JPA
    que la tabla donde se hace la relación muchos a muchos en BD
    es en este caso "relacion_proyectos".

    La columna de unión de Empleado y relación_proyectos es
    "cve_empleado"

    La columna para hacer una búsqueda inversa es "cve_proyecto"

    */
    @ManyToMany
    @JoinTable(name="relacion_proyectos",
         joinColumns={@JoinColumn(name="cve_empleado")},
         inverseJoinColumns={@JoinColumn(name="cve_proyecto")})
    private List<Proyecto> listaProyectos;

    public Integer getCveEmpleado() {
        return cveEmpleado;
    }

    public String getNombre() {
        return nombre;
    }

    public String getApellidoPaterno() {
        return apellidoPaterno;
    }

    public String getApellidoMaterno() {
        return apellidoMaterno;
    }

    public void setCveEmpleado(Integer cveEmpleado) {
        this.cveEmpleado = cveEmpleado;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public void setApellidoPaterno(String apellidoPaterno) {
        this.apellidoPaterno = apellidoPaterno;
    }

    public void setApellidoMaterno(String apellidoMaterno) {
        this.apellidoMaterno = apellidoMaterno;
    }

    public List<Proyecto> getListaProyectos() {
        return listaProyectos;
    }

    public void setListaProyectos(List<Proyecto> listaProyectos) {
        this.listaProyectos = listaProyectos;
    }

}

Ya con esto estamos listos para ejecutar una búsqueda, o por proyecto o por empleado.

voy a ejecutar yo una búsqueda por exactamente 1 empleado, al cual lo asigné a 3 proyectos, mi empleado
tiene el Id numero 1. procedo con el método find de mi entityManager, o si ustedes gustan pueden crear un namedQuery o query JPA, funciona exactamente igual:



   @Override
    public List obtenerProyectosPorEmpleado() {

        Empleado empl=null;

        try{
            empl=entityManager.find(Empleado.class, new Integer(1));
            System.out.println("Empleado: " + empl.getNombre());
            for(Proyecto p:empl.getListaProyectos()){
                System.out.println(p.getCveProyecto() + " " +
                p.getNombre());
            }

        }catch(NoResultException e){
            e.printStackTrace();
        }

        return empl.getListaProyectos();

    }

Veamos como se ejecuta en mi Glassfish:

  1. Miguel dice:

    Hola amigo, gracias por la explicación, de hecho justo hasta ese punto llegué haciendo los mapeos de mi BD, pero lo que no encuentro es como hacer una consulta que me permita obtener solo algunos de los registros de una de las tablas. Por ejemplo, si en tu modelo tuvieras estado del proyecto y quisieras obtener en una consulta los proyectos con estado Activo de un empleado específico, ¿es posible hacerlo con este mapeo? por que asi como esta solo puedo obtener todos los proyectos, pero en mi caso no necesito todos.

    saludos

    • Carlos Quintana dice:

      Pues mira trabajando con Eclipselink no he hallado la forma de hacer la consulta condicionada, para eso he ejecutado nativeQuery y siempre obtengo un array con Objetos, Hibernate según yo tampoco tiene forma de hacerlo, hay un ORM que puede que si, OpenJPA, seria que probaras. Saludos cordiales

    • Miguel dice:

      Gracias Carlos, descubri que todo ese manejo debe hacerse ya con los objetos que se obtienen de la base de datos, tal vez podria hacerse con consultas Criteria de JPA 2, pero no me da tiempo de clavarme a ver como se hace. Saludos.

  2. Fernando dice:

    Como guardar datos en esas tablas mediante codigo

  3. Paul Rojas dice:

    Gracias, me ayudo mucho esta informacion!!


6 × six =