El concepto de JPA Lazy Fetching siempre cuesta un poco entenderlo y es uno de los conceptos fundamentales de Java Persistence API .¿Cómo funciona exactamente? Vamos a explicarlo a detalle. Lo primero que tenemos que entender es que cuando trabajamos con JPA , definimos un modelo de dominio que relaciona las diferentes clases entre ellas.
Cuando JPA ejecuta una consulta se apoya en el modelo de dominio para generar un grafo de objetos en memoria.
El concepto de JPA Lazy Fetching esta asociado a como se carga este grafo de objetos en memoria. Hay situaciones en el cual lo cargamos completo pero hay otras situaciones en las cuales su carga es parcial. Vamos a a partir del siguiente modelo de clases:
package com.arquitecturajava; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; @Entity public class Libro { @Id private String titulo; private int paginas; @ManyToOne(optional=false) @JoinColumn(name="persona_nombre") private Persona persona; public String getTitulo() { return titulo; } public void setTitulo(String titulo) { this.titulo = titulo; } public int getPaginas() { return paginas; } public void setPaginas(int paginas) { this.paginas = paginas; } public Persona getPersona() { return persona; } public void setPersona(Persona persona) { this.persona = persona; } public Libro(String titulo, int paginas, Persona persona) { super(); this.titulo = titulo; this.paginas = paginas; this.persona = persona; } public Libro() { super(); } } package com.arquitecturajava; 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 Persona { @Id private String nombre; private String apellidos; private int edad; @OneToMany(mappedBy="persona",cascade = CascadeType.PERSIST) private List<Libro> libros= new ArrayList<Libro>(); public List<Libro> getLibros() { return libros; } public void setLibros(List<Libro> libros) { this.libros = libros; } public boolean addLibro(Libro e) { return libros.add(e); } public String getNombre() { return nombre; } public void setNombre(String nombre) { this.nombre = nombre; } public String getApellidos() { return apellidos; } public void setApellidos(String apellidos) { this.apellidos = apellidos; } public int getEdad() { return edad; } public void setEdad(int edad) { this.edad = edad; } public Persona(String nombre, String apellidos, int edad) { super(); this.nombre = nombre; this.apellidos = apellidos; this.edad = edad; } public Persona() { super(); } }
Podemos lanzar una consulta que solo cargue las Personas
package com.arquitecturajava; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class Principal4 { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("UnidadPersonas"); EntityManager em = emf.createEntityManager(); List<Persona> lista=em.createQuery("select p from Persona p", Persona.class).getResultList(); for (Persona p:lista) { System.out.println(p.getNombre()); System.out.println(p.getApellidos()); } } }
Esta consulta únicamente selecciona las personas , sin sus libros
Tenemos una única consulta que devuelve los datos de las personas:
¿Qué sucede si nosotros le pedimos a nuestro código que acceda a la relación de libros y muestre los libros por la consola?.
package com.arquitecturajava; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; public class Principal5 { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("UnidadPersonas"); EntityManager em = emf.createEntityManager(); List<Persona> lista=em.createQuery("select p from Persona p", Persona.class).getResultList(); for (Persona p:lista) { System.out.println(p.getNombre()); System.out.println(p.getApellidos()); for(Libro l :p.getLibros()) { System.out.println(l.getTitulo()); System.out.println(l.getPaginas()); } } } }
El framework nos devuelve los datos sorprendentemente , sin problemas .
¿Qué ha pasado?. No hemos seleccionado los libros previamente, así que no los tenemos en memoria ¿ Cómo es que los datos aparecen?. Acabamos de utilizar las capacidades de JPA Lazy Fetching. JPA al construir las relaciones ha ubicado proxies o intermedarios en cada una de ellas.
De tal forma que cuando accedamos a la relación estos proxies se activan acceden a la base y se traen los nuevos objetos
De esta manera optimizamos el acceso a los datos. Muchas veces la gente piensa que esta es la mejor forma de proceder . Pero lamentablemente no es siempre así ya que el uso de JPA Lazy Fetching si abusamos de él ejecutara cientos de consultas para cargar un grafo de objetos complejos a través de los proxies.
Es lo que comúnmente se denomina el problema de las N+1 Queries o el antipatron de OpenSession in View . Siempre que sea posible es mejor usar un enfoque tipo fetch join en las cuales el desarrollador indica que datos quiere de inicio a nivel de JPA QL
Otros artículos relacionados:
- JPA Criteria API , un enfoque diferente
- JPA SQL Injection y sus problemas
- Un ejemplo de JPA Entity Graph
- JPA WikiPedia