JPA-Hibernate + POJO + Projection + ConstructorResult + NativeQuery + Testcontainers + PostgreSQL


En esta ocasión mostraré un ejemplo usando Java SE 11 de como mapear una clase no entidad (un POJO) a partir de los datos que existen en la base de datos y que representan a una entidad utilizando ConstructorResult, Projection y una construcción "manual" con JPA. Las pruebas serán realizadas usando Testcontainers junto a Postgresql como base de datos.

El procedimiento es el siguiente (se omiten los metodos/imports/anotaciones que no son relevantes y usaré lombok solo para getter/setter):

1- La Entidad
Representa la entidad que mantendrá datos en la base de datos y de la cual extraeremos algunos de sus campos para nuestro pojo.
  1. @Entity
  2. @Table(schema = "jpa", name = "persona")
  3. public class Persona {
  4. @Id
  5. private int id;
  6. private String nombre;
  7. private String apellido;
  8. }


2- El POJO
Requiere tener un constructor con los argumentos que la query utilizará:
  1. public class PersonaPOJO {
  2. private String nn;
  3. private String aa;
  4. public PersonaPOJO() {}
  5. public PersonaPOJO(String nn, String aa) {
  6. this.nn = nn;
  7. this.aa = aa;
  8. }
  9. }
Los atributos pueden tener el nombre que estimen conveniente.


3- Definir el ConstructorResult
  1. @SqlResultSetMapping(name = "personapojo",
  2. classes = {
  3. @ConstructorResult(targetClass = PersonaPOJO.class,
  4. columns = {
  5. @ColumnResult(name = "nn", type = String.class),
  6. @ColumnResult(name = "aa", type = String.class)})})

personapojo: es el nombre que utilizaremos para hacer referencia a él
targetClass: nuestra clase objetivo (el POJO)
columns: definicion del resultado de la query a las propiedades que serán mapeadas a nuestro POJO usando el constructor

Para que sea reconocida en nuestro contexto de persistencia, personapojo tiene que ser escanenada por Hibernate y como PersonaPOJO.java no es una entidad no sería analizada si la definimos en ella. Por este motivo la dejaremos en Persona:

  1. @SqlResultSetMapping(name = "personapojo",
  2. classes = {
  3. @ConstructorResult(targetClass = PersonaPOJO.class,
  4. columns = {
  5. @ColumnResult(name = "nn", type = String.class),
  6. @ColumnResult(name = "aa", type = String.class)})})
  7. @Entity
  8. @Table(schema = "jpa", name = "persona")
  9. public class Persona {
  10. @Id
  11. private int id;
  12. private String nombre;
  13. private String apellido;
  14. }
4- persistence.xml

El archivo persistence.xml hay que dejarlo dentro de META-INF con el siguiente contenido:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.2"
  4. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
  5. <persistence-unit name="em"
  6. transaction-type="RESOURCE_LOCAL">
  7. <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
  8. <exclude-unlisted-classes>false</exclude-unlisted-classes>
  9. </persistence-unit>
  10. </persistence>
Definimos el nombre de nuestra unidad de persistencia (em) e indicamos con RESOURCE_LOCAL que nosotros administramos la creación de los EntityManager y que las transacciones serán administrados localmente y no por alguna implementacion JTA (es lo que necesitamos para nuestro ejemplo).

5- Contexto de Persistencia
Lo primero que hay que hacer es crear el EntityManagerFactory:
  1. private static EntityManagerFactory emf
  2. = Persistence.createEntityManagerFactory("em", cargarPropiedades());
  3. private static Map<String, String> cargarPropiedades() {
  4. return Map.of(
  5. "javax.persistence.jdbc.driver", System.getProperty("jdbc_driver"),
  6. "javax.persistence.jdbc.url", System.getProperty("jdbc_url"),
  7. "javax.persistence.jdbc.user", System.getProperty("jdbc_user"),
  8. "javax.persistence.jdbc.password", System.getProperty("jdbc_password"),
  9. "hibernate.hbm2ddl.auto", "validate"
  10. );
  11. }

usando el metodo cargarPropiedades() definimos programaticamente como será el acceso a la base de datos y no utilizamos el archivo persistence.xml para este propósito.

Luego para obtener el EntityManager:
  1. public static EntityManager obtener() {
  2. return emf.createEntityManager();
  3. }


6- Test

Para los tests usaré el siguiente script que permitirá tener datos precargados:
  1. create schema jpa;
  2. create table jpa.persona (
  3. id int primary key,
  4. nombre varchar default null,
  5. apellido varchar default null
  6. );
  7. insert into jpa.persona (id, nombre, apellido) values (1, 'Sebastián', 'Ávila');
Ahora las pruebas... El objetivo es sacar los datos disponibles para Persona (puede ser cualquier dato, no es necesario que esté asociado a la entidad) y usarlos para construir una instancia de PersonaPOJO (no es @Entity) utilizando JPA:
  1. // obtener el contexto de persistencia
  2. final var em = ContextoPersistencia.obtener();
  3. // obtener una entidad que ya existe en la base de datos (solo por entretención)
  4. final var persona = em.find(Persona.class, 1);
  5. validarPersona(persona);
  6. // Método 1: utilizar ConstructorResult junto a NativeQuery
  7. List<PersonaPOJO> personas = em
  8. .createNativeQuery("select nombre nn, apellido aa from jpa.persona", "personapojo")
  9. .getResultList();
  10. assertThat(personas.size()).isEqualTo(1);
  11. validarPersona(personas.get(0));
  12. // Método 2: utilizando JPQL y el constructor del POJO para la proyección
  13. personas = em
  14. .createQuery("select new com.sebastian.pojojpa.dominio.PersonaPOJO(p.nombre, p.apellido) from Persona p")
  15. .getResultList();
  16. validarPersona(personas.get(0));
  17. // Método 3: obtener un List<Object[]> y hacer la transformación “manual”
  18. final List<Object[]> obj = em
  19. .createNativeQuery("select nombre nn, apellido aa from jpa.persona")
  20. .getResultList();
  21. validarPersona(obj.get(0));
  22. em.close();
Los métodos de validación:
  1. private void validarPersona(final Object[] persona) {
  2. final var pj = new PersonaPOJO();
  3. pj.setNn((String) persona[0]);
  4. pj.setAa((String) persona[1]);
  5. validarPersona(pj);
  6. }
  7. private void validarPersona(final Persona p) {
  8. assertThat(p).isNotNull();
  9. assertThat(p.getId()).isEqualTo(1);
  10. assertThat(p.getNombre()).isEqualTo("Sebastián");
  11. assertThat(p.getApellido()).isEqualTo("Ávila");
  12. }
  13. private void validarPersona(final PersonaPOJO p) {
  14. assertThat(p).isNotNull();
  15. assertThat(p.getNn()).isEqualTo("Sebastián");
  16. assertThat(p.getAa()).isEqualTo("Ávila");
  17. }

Estructura del proyecto:



Y eso es todo☺el código está disponible en GitHub para ver el detalle de la implementación ✌.


No hay comentarios.:

Publicar un comentario