JPA Join Fetch y su uso.

JPA Join Fetch es una de las opciones de las que dispone el estándar de JPA a la hora de reducir el número de consultas que se generan contra la base de datos. Algo que es bastante habitual y que degrada el rendimiento sino tenemos cuidado. Vamos a ver un ejemplo ,para ello partiremos de dos clases Java (Experto e Impartición) que están relacionadas (un Experto imparte varias charlas).

El código Java de las clases utilizando anotaciones JPA  será :

package com.arquitectajava;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;

@Entity
public class Experto {
    @Id
    private String nombre;

    public Experto() {
        super();
    }
    @OneToMany(mappedBy = "experto", cascade = CascadeType.PERSIST)
    private List < Imparticion > imparticiones = new ArrayList < Imparticion > ();

    public List < Imparticion > getImparticiones() {
        return imparticiones;
    }

    public void setImparticiones(List < Imparticion > imparticiones) {
        this.imparticiones = imparticiones;
    }

    public String getNombre() {
        return nombre;
    }

    public Experto(String nombre) {
        super();
        this.nombre = nombre;
    }

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

        imparticiones.add(i);
    }

}
package com.arquitectajava;

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

@Entity
public class Imparticion {
    @Id
    private int id;
    private Date fecha;
    private String titulo;
    @ManyToOne
    @JoinColumn(name = "nombreExperto")
    private Experto experto;

    public Imparticion(int id, Date fecha, String titulo, Experto experto) {
        super();
        this.id = id;
        this.fecha = fecha;
        this.titulo = titulo;
        this.experto = experto;
    }

    public Experto getExperto() {
        return experto;
    }

    public void setExperto(Experto experto) {
        this.experto = experto;
    }
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Date getFecha() {
        return fecha;
    }

    public void setFecha(Date fecha) {
        this.fecha = fecha;
    }

    public String getTitulo() {
        return titulo;
    }

    public void setTitulo(String titulo) {
        this.titulo = titulo;
    }
}

Vamos a construir el programa principal que nos selecciona los Expertos para luego recorrer sus imparticiones.

package com.arquitectajava;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;

public class Principal {

    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("UnidadCharla");
        EntityManager em = emf.createEntityManager();
        TypedQuery < Experto > consulta = em.createQuery("select e from Experto e", Experto.class);

        List < Experto > lista = consulta.getResultList();

        for (Experto e: lista) {

            System.out.println(e.getNombre());
            List < Imparticion > imparticiones = e.getImparticiones();
            for (Imparticion i: imparticiones) {
                System.out.println(i.getTitulo());
            }

        }
        em.close();
    }

}

Si ejecutamos el programa podremos ver el resultado en la consola:

Los datos se imprimen , pero estamos realizando 3 consultas para obtenerlos .En primer lugar se seleccionan todos los Expertos y por cada Experto se realiza una consulta para saber sus imparticiones. Este es el problema clásico de las n+1 Queries. Cuando existan 100 imparticiones , se lanzarán 101 consultas lo cual es un verdadero problema. Vamos a solventar este problema utilizando JPA Join Fetch.

JPA Join Fetch

Para ello nos bastará con modificar la consulta de JPA que estamos utilizando y obligarla a que incluya las imparticiones añadiendo la clausula join fetch dentro de la consulta . Esto obligará a que Hibernate realice un Join en la base de datos. Ojo que hay que añadir una clausula distinct.

package com.arquitectajava;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;

public class Principal {

    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("UnidadCharla");
        EntityManager em = emf.createEntityManager();
        TypedQuery < Experto > consulta = em.createQuery("select distinct e from Experto e join fetch e.imparticiones", Experto.class);

        List < Experto > lista = consulta.getResultList();

        for (Experto e: lista) {

            System.out.println(e.getNombre());

            List < Imparticion > imparticiones = e.getImparticiones();
            for (Imparticion i: imparticiones) {
                System.out.println(i.getTitulo());
            }
        }
        em.close();
    }

}

Solución con JPA

Ahora la consulta es “select distinct e from Experto e join fetch e.imparticiones” obligando a JPA a incluir las imparticiones a través de un JOIN.

Ahora una sola consulta es suficiente ya que nos hemos apoyado en JPA Join Fetch y cargará un único grafo de objetos.

Recordemos que aunque los frameworks de persistencia aportan mucho , hay que saber utilizarlos. Un mal uso de estos frameworks puede generar los peores resultados.

Otros artículos relacionados :