Un error bastante común al desarrollar aplicaciones con JPA es volvernos perezosos y acostumbrarnos a siempre traer de vuelta los objetos enteros aun cuando solo necesitemos un par de propiedades.
Otro error común cuando somos principiantes en JPA (más que todo por ignorancia) es hacer varias consultas para obtener varios atributos desde la base de datos sin obtener el objeto completo.
Sin embargo podemos jugar un poco con JPA utilizando JPA projection para obtener solo los atributos que queremos
Imaginemos el siguiente escenario, donde tenemos una clase Foo equivalente a la tabla foo con 5 atributos-columnas.
Asi pues tendriamos
public class Foo{
private int id;
private String codigoUniversalProducto;
private String nombreUniversalProducto;
private BigDecimal existenciaActual;
private BigDecimal pedidosPendientes;
/*Getters y setters*/
} |
public class Foo{
private int id;
private String codigoUniversalProducto;
private String nombreUniversalProducto;
private BigDecimal existenciaActual;
private BigDecimal pedidosPendientes;
/*Getters y setters*/
}
Ahora bien imaginemos que nuestro servidor es de bajo rendimiento y por cada atributo nuestra implementacion de JPA tarda alrededor de 0.02 segundos en hacer el mapeo, por cada fila que obtengamos de la base de datos tardaremos 0.1 segundos.
Si esto lo traducimos a un resultset de 5000 filas tendriamos automaticamente 500 segundos solo para efectuar el mapeo. Obviamente estos tiempos son sobredimensionados pero imaginemos que este resultset es parte de una logica más grande y que es necesario hacer varias consultas a la misma tabla para algun procedimiento dentro de un EJB donde SOLO necesitamos los atributos de nombre y de existencia actual.
Si hacemos cuentas al solo obtener de regreso dos columnas de la base de datos en lugar del objeto completo, la operacion de mapeo tardaria 0.04 segundos en lugar de 0.1 segundos, con el resultset de 5000 filas tendriamos 200 segundos de operaciones de mapeo, interesante reducción no?.
¿Solución?
Solo obtener las columnas que necesitamos y eso lo hacemos con JPA projection, JPA projection es una funcionalidad de JPA para realizar mapeos de varias columnas dentro de una tabla y obtenerlas de tal forma que sean manipulables como objetos (el objetivo de los ORM), en JPA tenemos dos modos de funcionamiento SELECT con expresiones multiples, y utilizando constructores.
SELECT con expresiones multiples
Analogo a lo que realizamos con sql plano con JPA podemos hacer una consulta sobre las columnas que necesitamos. Por ejemplo
SELECT f.codigoUniversalProducto, f.nombreUniversalProducto FROM Foo AS f |
SELECT f.codigoUniversalProducto, f.nombreUniversalProducto FROM Foo as f
Si ejecutamos esta query lo que obtendremos sera un listado de objetos de tipo generico Object. Dependiendo si fueron una o más columnas este Object representara otro objeto de tipo Object[] o un tipo de dato en particular si nuestra query fue sobre una sola columna.
Por ejemplo:
List Object[] results = em.createQuery("SELECT f.codigoUniversalProducto, f.nombreUniversalProducto FROM Foo AS f").getResultList();
for (Object[] result : results) {
System.out.println("Codigo: " + result[0] + ", Nombre: " + result[1]);
} |
List Object[] results = em.createQuery("SELECT f.codigoUniversalProducto, f.nombreUniversalProducto FROM Foo AS f").getResultList();
for (Object[] result : results) {
System.out.println("Codigo: " + result[0] + ", Nombre: " + result[1]);
}
SELECT utilizando constructor
Sin embargo si queremos ir un poco más alla tambien podemos utilizar los constructores de nuestros objetos (mi metodo preferido). Con esta alternativa estariamos utilizando de nuevo los POJO’s que utilizabamos sin rediseñar nuestra arquitectura, por ejemplo con la query anterior deberiamos agregar el siguiente constructor a nuestro POJO:
public foo(String codigoUniversalProducto, String nombreUniversalProducto)
{
this.codigoUniversalProducto=codigoUniversalProducto;
this.nombreUniversalProducto=nombreUniversalProducto;
} |
public foo(String codigoUniversalProducto, String nombreUniversalProducto)
{
this.codigoUniversalProducto=codigoUniversalProducto;
this.nombreUniversalProducto=nombreUniversalProducto;
}
Y utilizamos este constructor desde nuestra expresión JPA
SELECT NEW org.shekalug.tuxtor.Foo(f.codigoUniversalProducto, c.nombreUniversalProducto)FROM Foo AS f; |
SELECT NEW org.shekalug.tuxtor.Foo(f.codigoUniversalProducto, c.nombreUniversalProducto)FROM Foo AS f;
La query anterior es totalmente equivalente y un ejemplo de uso seria:
String queryStr = "SELECT NEW org.shekalug.tuxtor.Foo(f.codigoUniversalProducto, c.nombreUniversalProducto)FROM Foo AS f;"
List Foo resultado = em.createQuery(queryStr).getResultList();
for(Foo fooObjeto:resultado)
{
System.out.println("Codigo: "+foo.getCodigo()+" Nombre: "+foo.getNombre());
} |
String queryStr = "SELECT NEW org.shekalug.tuxtor.Foo(f.codigoUniversalProducto, c.nombreUniversalProducto)FROM Foo AS f;"
List Foo resultado = em.createQuery(queryStr).getResultList();
for(Foo fooObjeto:resultado)
{
System.out.println("Codigo: "+foo.getCodigo()+" Nombre: "+foo.getNombre());
}
Vale la pena resaltar que se puete utilizar cualquier POJO independientemente si represente una tabla o no.