Querys con multiples columnas en JPA (JPA Projection)

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*/
}

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

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]);
}

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;
}

Y utilizamos este constructor desde nuestra expresión JPA

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());
}

Vale la pena resaltar que se puete utilizar cualquier POJO independientemente si represente una tabla o no.

15 respuestas a “Querys con multiples columnas en JPA (JPA Projection)”

  1. Google Chrome 9.0.597.83 Google Chrome 9.0.597.83 GNU/Linux x64 GNU/Linux x64
    Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.83 Safari/534.13

    Saludos.

    Quisiera saber si en la línea 1 del ejemplo 1, defines Object[] resultado o quieres definir un objeto Query y no un Object?
    gracias.

  2. Firefox 3.5.5 Firefox 3.5.5 Gentoo x64 Gentoo x64
    Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.12) Gecko/20101105 Gentoo firefox/3.5.5

    @Juan Botero: Porque tenia mal el ejemplo 😀 pero ya esta corregido lo que intentaba hacer era obtener de una vez el listado de Object[]. Gracias por notarlo.

  3. Firefox 3.6.17 Firefox 3.6.17 Ubuntu 10.10 Ubuntu 10.10
    Mozilla/5.0 (X11; U; Linux i686; es-MX; rv:1.9.2.17) Gecko/20110422 Ubuntu/10.10 (maverick) Firefox/3.6.17

    hola que tal, y si quisiera obtener el ultimo id insertado y este a la vez guardarlo en otra tabla, pero que no tenga problema de concurrencia como seria?

  4. Firefox 4.0 Firefox 4.0 GNU/Linux GNU/Linux
    Mozilla/5.0 (X11; Linux i686; rv:2.0) Gecko/20100101 Firefox/4.0

    Hay varias anotaciones en JPA para generacion de ID’s, pero en el caso que queres lo más facil es un idt generado por tabla para que JPA se encargue de generar el id correspondiente. La anotacion para esto se llama @TableGenerator

  5. Firefox 3.6.17 Firefox 3.6.17 Ubuntu 10.10 Ubuntu 10.10
    Mozilla/5.0 (X11; U; Linux i686; es-MX; rv:1.9.2.17) Gecko/20110422 Ubuntu/10.10 (maverick) Firefox/3.6.17

    gracias por contestar, pero no tengo problemas al generar los autoincrements el problema esta en que al momento que un usuario se registra tengo que hacer una consulta de ese ultimo id para asi guardarlo en otra tabla, pero existe el problema de concurrencia que mencionaba por que a lo mejor otro usuario se registre y yo pueda tomar el id equivocado de otro usuario. si entiendes?

  6. Firefox 4.0 Firefox 4.0 GNU/Linux GNU/Linux
    Mozilla/5.0 (X11; Linux i686; rv:2.0) Gecko/20100101 Firefox/4.0

    Y tienes que mostrar ese id a la hora que se registre el usuario?, si no lo tienes que mostrar es más simple generarlo hasta el instante en que vas a insertar el registro, eso podrias hacerlo con un select max.

    Sin embargo como te dije anteriormente es más facil generarlo con el @TableGenerator de JPA porque se encarga de estos problemas de concurrencia.

  7. Firefox 3.6.17 Firefox 3.6.17 Ubuntu 10.10 Ubuntu 10.10
    Mozilla/5.0 (X11; U; Linux i686; es-MX; rv:1.9.2.17) Gecko/20110422 Ubuntu/10.10 (maverick) Firefox/3.6.17

    no lo tengo que mostrar pues se genera hasta que el usuario le da al boton guardar, bueno mira tengo 2 formularios el primero es registro de usuario y el segundo es escolaridad, el primero es en donde se crea un nuevo usuario y ahi es donde se genera el id autoincrement entonces este id lo consulto con SELECT b.ID_TALENTO FROM t_talentos b ORDER BY b.ID_TALENTO DESC LIMIT 0,1 y lo tengo que pasar oculto en el formulario de escolaridad para asi hacer la mencion de que esa escolaridad pertenece a ese nuevo usuario pero que tal que otro usuario se registre entonces al hacer esa consulta podria ocurrir lo de la concurrencia y como podria resolver eso. o el @TableGenerator de JPA tambien lo resuelve.

  8. Firefox 4.0 Firefox 4.0 GNU/Linux GNU/Linux
    Mozilla/5.0 (X11; Linux i686; rv:2.0) Gecko/20100101 Firefox/4.0

    Si tambien lo resuelve y te deja el registro en una tabla de sequence generators para su posterior uso

  9. Firefox 3.6.17 Firefox 3.6.17 Ubuntu 10.10 Ubuntu 10.10
    Mozilla/5.0 (X11; U; Linux i686; es-MX; rv:1.9.2.17) Gecko/20110422 Ubuntu/10.10 (maverick) Firefox/3.6.17

    aaa muchas gracias. por el dato. vere si lo puedo resolver y no habra otra manera de resolver eso?

  10. Firefox 4.0 Firefox 4.0 GNU/Linux GNU/Linux
    Mozilla/5.0 (X11; Linux i686; rv:2.0) Gecko/20100101 Firefox/4.0

    En programación generalmente se utilizan registros de lock o algoritmos de concurrencia. En un sistema en tiempo real una vez vi que tenian una tabla solo para indicar que habia un lock en otra tabla y antes de intentar actualizar y generar un id se verificaba ese lock si el lock estaba activo se generaba un delay para volver a intentar actualizar, pero desde mi punto de vista es mucho más complicado

  11. Firefox 3.6.17 Firefox 3.6.17 Ubuntu 10.10 Ubuntu 10.10
    Mozilla/5.0 (X11; U; Linux i686; es-MX; rv:1.9.2.17) Gecko/20110422 Ubuntu/10.10 (maverick) Firefox/3.6.17

    bueno muchas gracias entonces.

  12. Firefox 4.0 Firefox 4.0 GNU/Linux GNU/Linux
    Mozilla/5.0 (X11; Linux i686; rv:2.0) Gecko/20100101 Firefox/4.0

    Qué tal. Un saludo, y gracias por tu aporte, me ha servido.

    Actualmente tengo un problema, y es que mi aplicación no me muestra los datos ingresados directamente a la base de datos, solo cuando se ingresan desde la misma aplicación, ni siquiera al reiniciar el servidor… sino que hasta mucho tiempo despues, como por ejemplo, cuando regreso a seguir trabajando y corro mi aplicación otra vez

    Apenas estoy conociendo este framework, y pues, por el momento no he encontrado en la web algo que me aclare el problema. Quizás, tengas idea de cual podría ser el asunto? Te agradecería si pudieras orientarme

    Trabajo con Glassfisfh v3, y uso JPA 2 EclipseLink; el tipo de transacción es JTA.

    Saludos.

  13. Firefox 4.0 Firefox 4.0 GNU/Linux GNU/Linux
    Mozilla/5.0 (X11; Linux i686; rv:2.0) Gecko/20100101 Firefox/4.0

    Actualizando mi post anterior:

    El problema en realidad era mio, por la forma en que insertaba los valores a la base de datos, y los parámetros de mi query en la aplicación :p

    Saludos

  14. Firefox 26.0 Firefox 26.0 Ubuntu x64 Ubuntu x64
    Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:26.0) Gecko/20100101 Firefox/26.0

    Ey muuuchas gracias me sirvió mucho este post, tenía problemas para procesar una consulta de dos columnas precisamente. Ahora todo funciona 😀

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *