miércoles, 3 de agosto de 2016

SqlResultSetMapping y como obtener valores de BBDD que no forman parte de la tabla/Entidad con JPA

Por motivos particulares puede que tengamos nuestra aplicación perfectamente montada con JPA pero necesitemos obtener un valor extra asociado a la clase JPA.

Para nuestro ejemplo, además de los datos del objeto vamos a intentar obtener un campo que nos indique en función de la fecha actual y la de la publicación del libro, si este es nuevo o no (fecha actual menos la de publicación es inferior a un año). Para ello podemos hacerlo de dos formas. La primera sería de manera automática y la segunda a través de @SqlResultSetMapping.

Para la primera forma, solo tendremos que crear el campo en la clase JPA:

@Entity
@Table(name = "libro", catalog = "almacen")
public class Libro implements {
 private Boolean nuevo;

Tras esto crearemos nuestro método en el DAO que además de obtener los propios valores de la tabla, obtenga el campo calculado 'nuevo'.

public List<Libro> getLibros() {
 String sql = "select l.*, (DATE_SUB(NOW(), INTERVAL 1 YEAR) <= l.publicacion) as nuevo from libro l";
 Query retorno = getJpaTemplate().getEntityManager().createNativeQuery(sql, Libro.class);
 return retorno.getResultList();
}

Como podéis ver realizamos una query nativa (en mi caso con MYSQL) para poder obtener el campo calculado.

Este enfoque es el más cómodo y JPA se encargará siempre de obtener el campo nuevo y almacenarlo en el objeto Libro. Pero tendremos el inconveniente que cada vez que queramos obtener el objeto Libro, deberemos rellenar de alguna forma el campo 'nuevo' o nos dará una excepción del tipo 'Column Not Found'.

Para evitar esto, tendremos el segundo enfoque. El cual nos permitirá obtener el objeto Libro sin problemas y además obtener relleno el campo 'nuevo' solo cuando queramos.

Para ello el primer paso será indicar que el atributo 'nuevo' es '@Transient'. Esto le indicará a JPA que dicho atributo debe obviarlo a la hora de tratar con el objeto Libro.

Por otro lado modificaremos la clase Libro para añadir la anotación SqlResultSetMapping. Esta anotación nos permitirá indicar a JPA en tiempo de ejecución, como debe mapear los distintos atributos devueltos por el ResultSet al utilizar consultas nativas. Podéis encontrar más información aquí, pero esta es una breve descripción de sus componentes:
  • EntityResult: Indica de que tipo será el objeto devuelto por el resultSet.
  • FieldResult: Indica como mapear los campos de dicha entidad.
  • ColumnResult: Indica como mapear los campos que no pertenecen a la entidad.
Al utilizar 'entityResult' y 'columnResult' hará que al obtener el listado de resultados, se obtenga un array de objetos y las distintas posiciones de dicho array serán primero las entidades configuradas y posteriormente las columnas configuradas.

Nuestro SqlResultMapping quedará así:


@Entity
@Table(name = "libro", catalog = "almacen")
@SqlResultSetMapping(name = "resultMappingTest", entities = @EntityResult(entityClass = Libro.class), columns = { @ColumnResult(name = "nuevo") })
public class Libro implements java.io.Serializable {
 @Transient
 private Boolean nuevo;


Y nuestro DAO leerá los resultados de la siguiente forma:


public List<Libro> getLibros() {
 List<Libro> libros = new ArrayList<Libro>();
 String sql = "select l.*, (DATE_SUB(NOW(), INTERVAL 1 YEAR) <= l.publicacion) as nuevo from libro l";
 Query retorno = getJpaTemplate().getEntityManager().createNativeQuery(sql, "resultMappingTest");
 List<Object[]> listado = retorno.getResultList();
 for (Object[] objetos : listado) {
  Libro libro = (Libro) objetos[0];
  libro.setNuevo(objetos[1]);
  libros.add(libro);
 }
 return libros;
}

No hay comentarios:

Publicar un comentario