Uso de Proyecciones en Queries Personalizados con Java y SQL

Clase 19 de 25Curso de Java Spring Data JPA: Bases de Datos

Contenido del curso

Spring Data Repositories

Resumen

Cuando una consulta involucra múltiples tablas y el resultado no coincide con ninguna entidad del modelo, las projections (proyecciones) en Spring Data JPA ofrecen una solución limpia y eficiente. Permiten definir exactamente qué campos recuperar sin cargar entidades completas ni procesar relaciones en Java, delegando todo el trabajo a la base de datos.

¿Qué son las projections y por qué usarlas?

Una projection es una interfaz personalizada que actúa como un DTO (Data Transfer Object) definido a medida. En lugar de mapear una tabla completa, declaramos únicamente los atributos que necesitamos obtener de un query específico [0:15].

En el ejemplo del proyecto de pizzería, obtener el resumen de una orden requiere datos de varias tablas:

  • pizza_order: identificador de la orden, fecha y total.
  • customer: nombre del cliente.
  • pizza: nombres de las pizzas ordenadas.

Sin proyecciones, habría que recuperar cada entidad por separado y combinar la información en Java. Con una projection, un solo SQL resuelve todo.

¿Cómo se crea la interfaz de proyección?

Dentro de la capa de persistencia se crea un paquete llamado projection y en él una nueva interfaz [1:22]. Los atributos se expresan como métodos getter, sin necesidad de que coincidan exactamente con los nombres de las columnas en base de datos:

java public interface OrderSummary { Integer getIdOrder(); String getCustomerName(); LocalDateTime getOrderDate(); Double getOrderTotal(); String getPizzaNames(); }

La única regla importante es que los alias del SELECT en el SQL nativo deben coincidir con los nombres de estos métodos, respetando camelCase con la primera letra en minúscula [3:45].

¿Cómo se construye el query nativo con múltiples joins?

El SQL utiliza tres joins para relacionar las tablas necesarias [2:30]:

  • customer: vincula el id_customer de la orden con el de la tabla customer.
  • order_item: conecta el identificador de la orden con los ítems.
  • pizza: une order_item con la tabla pizza a través del id_pizza.

Se emplea la función GROUP_CONCAT para concatenar por comas los nombres de las pizzas dentro de un mismo resultado, y un GROUP BY agrupa por identificador de orden, nombre del cliente, fecha y total [3:05].

Los alias en el SELECT deben escribirse exactamente como los getters de la proyección:

sql SELECT po.id_order AS idOrder, cu.name AS customerName, po.date AS orderDate, po.total AS orderTotal, GROUP_CONCAT(pi.name) AS pizzaNames FROM pizza_order po JOIN customer cu ON po.id_customer = cu.id_customer JOIN order_item oi ON po.id_order = oi.id_order JOIN pizza pi ON oi.id_pizza = pi.id_pizza WHERE po.id_order = :orderId GROUP BY po.id_order, cu.name, po.date, po.total

¿Cómo se integra en el repositorio, servicio y controlador?

En el repositorio se crea un método anotado con @Query, indicando que es un native query [5:10]:

java @Query(value = "SELECT po.id_order AS idOrder, cu.name AS customerName, " + "po.date AS orderDate, po.total AS orderTotal, " + "GROUP_CONCAT(pi.name) AS pizzaNames " + "FROM pizza_order po " + "JOIN customer cu ON po.id_customer = cu.id_customer " + "JOIN order_item oi ON po.id_order = oi.id_order " + "JOIN pizza pi ON oi.id_pizza = pi.id_pizza " + "WHERE po.id_order = :orderId " + "GROUP BY po.id_order, cu.name, po.date, po.total", nativeQuery = true) OrderSummary findSummary(@Param("orderId") int orderId);

Un detalle crítico al copiar SQL de varias líneas: cuidar los espacios entre concatenaciones. Si palabras como pizzaNames y FROM quedan pegadas, el SQL falla [6:40].

En el servicio, el método simplemente delega al repositorio:

java public OrderSummary getSummary(int orderId) { return this.orderRepository.findSummary(orderId); }

En el controlador se expone un endpoint GET que recibe el identificador de la orden y retorna el OrderSummary [7:30].

¿Qué ventajas ofrecen las projections basadas en interfaces?

Al probar en Postman con la orden número dos, la respuesta incluye el nombre del cliente, la fecha, el total y las pizzas concatenadas, todo en una sola petición [8:40].

  • Flexibilidad: defines exactamente los campos que necesitas.
  • Rendimiento: la base de datos procesa los joins y la concatenación, evitando múltiples consultas desde Java.
  • Legibilidad: el código queda limpio al no cargar entidades completas con todas sus relaciones.

Las proyecciones son especialmente valiosas cuando los reportes o resúmenes combinan información de distintas entidades. Si has trabajado con queries complejos en tus proyectos, comparte cómo los has resuelto.