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.
Representa la entidad que mantendrá datos en la base de datos y de la cual extraeremos algunos de sus campos para nuestro pojo.
@Entity
@Table(schema = "jpa", name = "persona")
public class Persona {
@Id
private int id;
private String nombre;
private String apellido;
}
2- El POJO
Requiere tener un
constructor con los argumentos que la query utilizará:
public class PersonaPOJO {
private String nn;
private String aa;
public PersonaPOJO() {}
public PersonaPOJO(String nn, String aa) {
this.nn = nn;
this.aa = aa;
}
}
Los atributos pueden tener el nombre que estimen conveniente.
3- Definir el ConstructorResult
@SqlResultSetMapping(name = "personapojo",
classes = {
@ConstructorResult(targetClass = PersonaPOJO.class,
columns = {
@ColumnResult(name = "nn", type = String.class),
@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:
@SqlResultSetMapping(name = "personapojo",
classes = {
@ConstructorResult(targetClass = PersonaPOJO.class,
columns = {
@ColumnResult(name = "nn", type = String.class),
@ColumnResult(name = "aa", type = String.class)})})
@Entity
@Table(schema = "jpa", name = "persona")
public class Persona {
@Id
private int id;
private String nombre;
private String apellido;
}
4- persistence.xml
El archivo persistence.xml hay que dejarlo dentro de META-INF con el
siguiente contenido:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.2"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="em"
transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
</persistence-unit>
</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:
private static EntityManagerFactory emf
= Persistence.createEntityManagerFactory("em", cargarPropiedades());
private static Map<String, String> cargarPropiedades() {
return Map.of(
"javax.persistence.jdbc.driver", System.getProperty("jdbc_driver"),
"javax.persistence.jdbc.url", System.getProperty("jdbc_url"),
"javax.persistence.jdbc.user", System.getProperty("jdbc_user"),
"javax.persistence.jdbc.password", System.getProperty("jdbc_password"),
"hibernate.hbm2ddl.auto", "validate"
);
}
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:
public static EntityManager obtener() {
return emf.createEntityManager();
}
6- Test
Para los tests usaré el siguiente script que permitirá tener datos
precargados:
create schema jpa;
create table jpa.persona (
id int primary key,
nombre varchar default null,
apellido varchar default null
);
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:
// obtener el contexto de persistencia
final var em = ContextoPersistencia.obtener();
// obtener una entidad que ya existe en la base de datos (solo por entretención)
final var persona = em.find(Persona.class, 1);
validarPersona(persona);
// Método 1: utilizar ConstructorResult junto a NativeQuery
List<PersonaPOJO> personas = em
.createNativeQuery("select nombre nn, apellido aa from jpa.persona", "personapojo")
.getResultList();
assertThat(personas.size()).isEqualTo(1);
validarPersona(personas.get(0));
// Método 2: utilizando JPQL y el constructor del POJO para la proyección
personas = em
.createQuery("select new com.sebastian.pojojpa.dominio.PersonaPOJO(p.nombre, p.apellido) from Persona p")
.getResultList();
validarPersona(personas.get(0));
// Método 3: obtener un List<Object[]> y hacer la transformación “manual”
final List<Object[]> obj = em
.createNativeQuery("select nombre nn, apellido aa from jpa.persona")
.getResultList();
validarPersona(obj.get(0));
em.close();
Los métodos de validación:
private void validarPersona(final Object[] persona) {
final var pj = new PersonaPOJO();
pj.setNn((String) persona[0]);
pj.setAa((String) persona[1]);
validarPersona(pj);
}
private void validarPersona(final Persona p) {
assertThat(p).isNotNull();
assertThat(p.getId()).isEqualTo(1);
assertThat(p.getNombre()).isEqualTo("Sebastián");
assertThat(p.getApellido()).isEqualTo("Ávila");
}
private void validarPersona(final PersonaPOJO p) {
assertThat(p).isNotNull();
assertThat(p.getNn()).isEqualTo("Sebastián");
assertThat(p.getAa()).isEqualTo("Ávila");
}
Estructura del proyecto:
Y eso es todo☺el código está disponible en GitHub para ver el detalle de la implementación ✌.