Un ejemplo de JPA Entity Graph

Mucha gente desconoce el concepto de JPA Entity Graph , y como nos pueden ayudar a mejorar el rendimiento de las consultas de JPA que creamos. Para entender como funcionan hay que recordar algunas cosas de JPA. En primer lugar que todas las consultas que realizamos oneToMany son lazy feching , es decir los datos se cargan según los vamos solicitando generándose en muchas ocasiones las indeseadas n+1 queries.

JPA OneToMany

Para entender mejor el concepto ,vamos a construir un ejemplo usando dos clases: Experto e Impartición. Ambas clases están relacionadas a través de una relación @oneToMany. Un Experto es capaz de  realizar varias imparticiones.

without entity graph

Vamos a mostrar el contenido de ambas clases:


package com.arquitecturajava;


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

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.NamedAttributeNode;
import javax.persistence.NamedEntityGraph;
import javax.persistence.OneToMany;

@Entity
public class Experto {
	@Id
	private String nombre;
	
	@OneToMany(mappedBy="experto")
	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);
	}

	public Experto() {
		super();
	}
	
	
}



package com.arquitecturajava;


import java.util.Date;

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

@Entity

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

	public Imparticion() {
		super();
	}

	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 crear un programa main y solicitamos una lista  de los Expertos:


package com.arquitecturajava;

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());
			for (Imparticion i : e.getImparticiones()) {

				System.out.println(i.getTitulo());
			}

		}
		em.close();

	}

}

Una vez solicitada la lista accedemos al conjunto de imparticiones que cada Experto tiene. El resultado lo podemos ver por la consola.

jpaentitygraphconsole

Lamentablemente nos encontramos ante una situación en la que se producen n+1 Queries. En este caso tres consultas una para obtener los expertos y otras dos para cada grupo de imparticiones. Hubiera sido mejor utilizar un Join. Esto se soluciona habitualmente usando un  fetchJoin  que nos permite realizar un Join con JPA. Sin embargo nos queda un problema. ¿Cuantas consultas diferentes haremos sobre estas dos tablas?

jpa entity graph queries

La respuesta es que muchas. En la mayoría de estas consultas necesitaremos que se realice el mismo join.

jpa entity graph repeat

Para evitar estas situaciones se han creado los JPA Entity Graph que nos permiten definir un grafo de componentes y atributos que cargar en una query.

jpa entity graph idea

Usando JPA Entity Graph

Para ello necesitaremos añadir la anotación de NamedEntityGraph en la clase Experto.

</pre>
@Entity
@NamedEntityGraph(

name = "ExpertoConImparticiones", attributeNodes = {
@NamedAttributeNode("imparticiones"),
})
public class Experto {
<pre>

En segundo lugar debemos modificar el programa principal para que cuando la consulta se realice se apoye en un EntityGraph.


&nbsp;

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

public class Principal2 {

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", Experto.class);

consulta.setHint("javax.persistence.loadgraph",
em.getEntityGraph("ExpertoConImparticiones"));

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

for (Experto e : lista) {

System.out.println(e.getNombre());
for (Imparticion i : e.getImparticiones()) {

System.out.println(i.getTitulo());
}

}
em.close();

}

}

Una vez hecho esto la consulta se ejecutará como un join.

jpaentitygraphconsolaok

Así  podremos reutilizar los JPA Entity Graph según nuestras necesidades.

Otros artículos relacionados : ORM.XML , Introducción a JPA , JPA NamedQueries

About Cecilio Álvarez Caules

Cecilio Álvarez Caules Sun Certified Enterprise Architech (J2EE/JEE).

13 Responses to Un ejemplo de JPA Entity Graph

  1. Jesús Perales 9 septiembre, 2016 at 19:16 #

    Muchas gracias creo que esto me podria ayudar

    • Cecilio Álvarez Caules 9 septiembre, 2016 at 21:32 #

      Me alegro que te sea útil 🙂

      • Jesús Perales 21 septiembre, 2016 at 0:58 #

        Al final no pude integrarlo ya que tenemos JPA 2.0 , creo que es importante recalcar que solo esta disponible apartir de JPA 2.1 :'(

        • Cecilio Álvarez Caules 21 septiembre, 2016 at 7:16 #

          Gracias por el aporte , es cierto q los grafos son relativamente nuevos 🙂

  2. Sergio 9 septiembre, 2016 at 20:59 #

    Lo mejor de este blog son los ejemplo tan ilustrados que se muestran. No solamente se centra en explicar sino que expone la motivación que ha llevado a crear tal función, en este caso las entityGraph.

    Gracias Cecilio!

    • Cecilio Álvarez Caules 9 septiembre, 2016 at 21:31 #

      De nada , me alegro que sea útil 🙂

  3. Franco 10 septiembre, 2016 at 8:52 #

    Gracias por el artículo.
    Una duda: en la primera parte donde el for intenta iterar la lista del lado Many, no deberia estar vacia? Si las relaciones OneToMany son lazy como se explica antes.

    Saludos

    • David 16 noviembre, 2016 at 11:36 #

      Hola Franco, que me corrijan si me equivoco, pero que sea LAZY quiere decir que, de primeras, no rellenará las listas que tenga mapeadas con oneToMany en este caso.

      Es decir, que el objeto Experto que obtienes de la query, para que sea más ligero y tener a LAZY su relación con Imparticion, no tiene rellena su lista de imparticiones asociada hasta que explicitamente la pidas, como es el caso del for. Como en el for, sobre uno de los objetos experto, solicitas con el getImparticiones su lista, es cuando ejecuta la query bajo demanda.

      Si Experto hubiese sido desconectada del gestor de persistencia (dettached), y solicitases la lista de imparticiones, en ese caso, obtendrías un error por no haber sido instanciadas previamente (EAGER).

      Saludos.

      • Cecilio Álvarez Caules 16 noviembre, 2016 at 14:34 #

        Si una relación Lazy se carga cuando solicitas acceso a esa colección de forma expresa

  4. Daniel 21 septiembre, 2016 at 11:53 #

    Muchas gracias por tus artículos.

    Con EclipseLink en vez de Hibernate no logro que funcione. He leído que hay un bug en algunas versiones pero que está corregido la última versión y sin embargo no logro hacer que me funcionen los entity graph. Me salen las mismas consultas tanto si lo pongo como si no (con el fetch join si me funciona). ¿Sabes si hay alguna otra cuestión que tenga que tener en cuenta?

    Muchas gracias. Saludos.

    • Cecilio Álvarez Caules 21 septiembre, 2016 at 12:49 #

      no sabría decirte 🙁 tienes JPA 2.1 verdad?

      • Daniel 21 septiembre, 2016 at 14:51 #

        Si… Voy a intentarlo usando Hibernate a ver…

Trackbacks/Pingbacks

  1. JPA Proxy y su funcionamiento - Arquitectura Java - 13 septiembre, 2016

    […] que comienza a trabajar con JPA. ¿Cómo funciona un JPA Proxy? .Vamos a apoyarnos en el ejemplo del artículo anterior y hacer una pequeña modificación . Recordemos que partimos de dos clases Experto e Imparticion […]

Deja un comentario