El uso de JPA DTO es algo bastante común cuando trabajamos con JPA. Recordemos que habitualmente cuando realizamos una consulta con Java Persistence API el resultado es un grafo de objetos.
En muchos casos esto nos es suficiente , pero hay algunas ocaciones en las que nos puede ser mucho más útil devolver un DTO (Data Transfer Object) . Este incluye únicamente la información absolutamente necesaria y su rendimiento a nivel de red aumentará. Vamos a construir un ejemplo con las clases Persona y Libro. Para ello lo primero es importar las dependencias de maven.
<dependencies> <dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.1-api</artifactId> <version>1.0.0.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.2.10.Final</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.42</version> </dependencies>
El siguiente paso es configurar el persistence.xml:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0"> <persistence-unit name="UnidadPersonas"> <class>es.curso.bo.Persona</class> <properties> <property name= "hibernate.show_sql" value="true" /> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" /> <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" /> <property name="javax.persistence.jdbc.user" value="root" /> <property name="javax.persistence.jdbc.password" value="mysql" /> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/java2" /> </properties> </persistence-unit </persistence>
Una vez configuradas las dependencias vamos a construir nuestras clases:
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; import org.hibernate.annotations.Cascade; @Entity public class Persona { @Id private String nombre; private String apellidos; private int edad; @OneToMany(mappedBy="persona",cascade = CascadeType.PERSIST) private List&amp;lt;Libro&gt; libros= new ArrayList&amp;lt;Libro&gt;(); public List&amp;lt;Libro&gt; getLibros() { return libros; } public void setLibros(List&amp;lt;Libro&gt; 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(); } }
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 @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(); } }
Vamos a ejecutar una consulta normal utilizando JPQL que nos seleccione las personas con sus libros:
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 Principal2 { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("UnidadPersonas"); EntityManager em = emf.createEntityManager(); List&amp;lt;Persona&gt; lista=em.createQuery("select p from Persona p", Persona.class).getResultList(); for (Persona p:lista) { System.out.println(p.getApellidos()); for(Libro l :p.getLibros()) { System.out.println(l.getTitulo()); System.out.println(l.getPaginas()); } } } }
El resultado se muestra en la consola:
Se han tenido que realizar varias consultas para obtener tanto las Personas como los Libros .
JPA DTO un enfoque diferente
En el ejemplo anterior podríamos haber utilizado un join fetch para mejorar el rendimiento y reducir las queries , pero el problema de fondo que en este caso queremos abordar es diferente. En nuestro caso queremos seleccionar únicamente un pequeño subconjunto de los datos del grafo utilizando JPA DTO.
Para ello podemos apoyarnos en un DTO (Data Transfer Object) ,creando una nueva clase:
package com.arquitecturajava; public class LibroPersonaDTO { private String apellidos; private String titulo; private int paginas; public int getPaginas() { return paginas; } public void setPaginas(int paginas) { this.paginas = paginas; } public String getTitulo() { return titulo; } public void setTitulo(String titulo) { this.titulo = titulo; } public String getApellidos() { return apellidos; } public void setApellidos(String apellidos) { this.apellidos = apellidos; } public LibroPersonaDTO(String apellidos, String titulo, int paginas) { super(); this.apellidos = apellidos; this.titulo = titulo; this.paginas = paginas; } }
En este caso nos queremos quedar con los títulos ,las páginas y el apellido de la persona a la que pertenece el libro.
¿Cómo podemos hacer esto con JPQL? . JPQL soporta un operador new que nos permite enlazar un DTO en la query, vamos a verlo:
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 Principal3 { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("UnidadPersonas"); EntityManager em = emf.createEntityManager(); List&amp;lt;LibroPersonaDTO&gt; lista=em.createQuery("select distinct new com.arquitecturajava.LibroPersonaDTO( p.apellidos,l.titulo,l.paginas ) from Persona p, Libro l", LibroPersonaDTO.class).getResultList(); for (LibroPersonaDTO dto: lista) { System.out.println(dto.getTitulo()); System.out.println(dto.getPaginas()); System.out.println(dto.getApellidos()); } } }
El resultado de la consulta incluirá una lista de DTOS con únicamente los datos que necesitamos, hemos mejorado el rendimiento de nuestra consulta utilizando JPA DTO:
Otros artículos relacionados:
Excelente ! muchos developers hacen el cambio de manera manual o de plano mandan las clases mapeadas a la parte front ! lo que obviamente es una mala practica !! Gracias
gracias 🙂
Muy bueno Cecilio, no lo conocía.
Solo una duda, en la consulta no hace un producto cartesiano? No debería ser … from Persona p join libros l …
Si yo creo que hubiera quedado la consulta más clara 🙂
Hola
con el api hibernate deberia ser similar ? esto no me funciona :S
String hql = “select distinct new com.arquitecturajava.LibroPersonaDTO( p.apellidos,l.titulo,l.paginas ) from Persona p, Libro l”;
List lista = sessionFactory.getCurrentSession().createQuery(hql);
Pues no lo he probado esa es la verdad 🙁
En Hibernate existe algo a mi parecer más elegante llamado ResultTransformer
http://in.relation.to/2006/03/17/hibernate-32-transformers-for-hql-and-sql/
Con JPA es posible hacer algo parecido utilizando Tuple y reflection.
Gracias por el aporte 🙂