Uso de consultas SQL nativas
Aunque Spring Data JPA genera muchas consultas automáticamente, en proyectos reales muchas veces necesitamos usar SQL directamente.
Esto se llama:
consultas SQL nativas
Se utilizan cuando:
- necesitamos consultas complejas
- queremos optimizar rendimiento
- queremos usar funciones específicas de la base de datos
- queremos controlar completamente el SQL
1. Cuándo usar SQL nativo #
JPA es muy cómodo, pero no siempre es suficiente.
Ejemplo donde JPA funciona bien:
1findByEmail(String email)
Pero consultas complejas como:
1SELECT COUNT(*) 2FROM orders 3GROUP BY user_id
son más fáciles con SQL nativo.
2. Usar @Query #
Spring permite escribir consultas personalizadas usando la anotación:
1@Query
3. Ejemplo #
1@Repository 2public interface UserRepository extends JpaRepository<User, Long> { 3 4 @Query("SELECT u FROM User u WHERE u.name = :name") 5 List<User> findUsersByName(String name); 6 7}
Esto es JPQL (una versión SQL para entidades).
4. SQL nativo #
Si queremos usar SQL real usamos:
1nativeQuery = true
5. Ejemplo #
1@Query(value = "SELECT * FROM users WHERE name = :name", 2 nativeQuery = true) 3List<User> findUsersByName(String name);
Esto ejecuta SQL directamente en la base de datos.
6. Consulta con parámetros #
Podemos usar parámetros en las consultas.
7. Ejemplo #
1@Query(value = "SELECT * FROM users WHERE email = :email", 2 nativeQuery = true) 3User findByEmail(String email);
Uso:
1repository.findByEmail("ana@email.com");
8. Insert SQL nativo #
Para ejecutar INSERT usamos:
1@Modifying
9. Ejemplo #
1@Modifying 2@Query(value = "INSERT INTO users(name,email) VALUES(:name,:email)", 3 nativeQuery = true) 4void insertUser(String name, String email);
También debemos usar:
1@Transactional
porque modifica datos.
10. Ejemplo completo #
1@Transactional 2@Modifying 3@Query(value = "INSERT INTO users(name,email) VALUES(:name,:email)", 4 nativeQuery = true) 5void insertUser(String name, String email);
11. Update SQL nativo #
Ejemplo actualizar usuario.
1@Transactional 2@Modifying 3@Query(value = "UPDATE users SET name = :name WHERE id = :id", 4 nativeQuery = true) 5void updateUser(Long id, String name);
12. Delete SQL nativo #
1@Transactional 2@Modifying 3@Query(value = "DELETE FROM users WHERE id = :id", 4 nativeQuery = true) 5void deleteUser(Long id);
13. Consultas con JOIN #
Una gran ventaja del SQL nativo es usar JOINs complejos.
14. Ejemplo #
Tablas:
1users 2orders
Consulta:
1@Query(value = """ 2 SELECT u.name, COUNT(o.id) 3 FROM users u 4 JOIN orders o ON u.id = o.user_id 5 GROUP BY u.name 6""", nativeQuery = true) 7List<Object[]> countOrdersByUser();
Resultado:
1[ 2 ["Ana",5], 3 ["Carlos",3] 4]
15. Proyecciones (interfaces) #
Para evitar usar Object[], podemos usar proyecciones.
16. Crear interfaz #
1public interface UserOrderStats { 2 3 String getName(); 4 Integer getTotalOrders(); 5 6}
17. Consulta #
1@Query(value = """ 2 SELECT u.name as name, 3 COUNT(o.id) as totalOrders 4 FROM users u 5 JOIN orders o ON u.id = o.user_id 6 GROUP BY u.name 7""", nativeQuery = true) 8List<UserOrderStats> getUserStats();
Ahora Spring convierte automáticamente el resultado.
18. Usar EntityManager #
Otra forma de ejecutar SQL es usando:
1EntityManager
Esto permite ejecutar consultas dinámicas.
19. Ejemplo #
1@PersistenceContext 2private EntityManager entityManager;
Consulta:
1public List<User> getUsers() { 2 3 String sql = "SELECT * FROM users"; 4 5 return entityManager 6 .createNativeQuery(sql, User.class) 7 .getResultList(); 8 9}
20. Ventajas del SQL nativo #
| ventaja | explicación |
|---|---|
| control total | escribes el SQL |
| mejor rendimiento | optimización directa |
| consultas complejas | JOIN, GROUP BY, etc |
| funciones específicas | funciones del motor |
21. Desventajas #
| problema | explicación |
|---|---|
| menos portable | depende de la base de datos |
| más mantenimiento | hay que escribir SQL |
| más acoplamiento | dependes de la estructura DB |
22. Cuándo usar JPA vs SQL #
Regla práctica:
Usar JPA cuando #
- CRUD simple
- consultas sencillas
- rapidez de desarrollo
Usar SQL nativo cuando #
- consultas complejas
- optimización
- agregaciones
- informes
23. Ejemplo real completo #
Repository:
1@Repository 2public interface UserRepository extends JpaRepository<User, Long> { 3 4 @Query(value = "SELECT * FROM users WHERE email = :email", 5 nativeQuery = true) 6 User findByEmail(String email); 7 8 @Transactional 9 @Modifying 10 @Query(value = "UPDATE users SET name = :name WHERE id = :id", 11 nativeQuery = true) 12 void updateName(Long id, String name); 13 14}
Service:
1@Service 2public class UserService { 3 4 private final UserRepository repository; 5 6 public UserService(UserRepository repository) { 7 this.repository = repository; 8 } 9 10 public User getUserByEmail(String email) { 11 return repository.findByEmail(email); 12 } 13 14}
Resumen del tema
Conceptos clave #
- Consultas Personalizadas con
@Query: uso de JPQL (orientado a objetos de entidad) frente a SQL Nativo (nativeQuery = true). - Operaciones de Modificación (
@Modifying+@Transactional): obligatorio para sentencias DML nativas (INSERT,UPDATE,DELETE) en repositorios Spring Data. - Paso de Parámetros: enlace de variables nominales mediante la sintaxis
:nombreParametro. - Mapeo de Resultados y Proyecciones:
- Proyecciones basadas en interfaces (Interface-based Projections) para mapear alias SQL a getters tipados (
getName(),getTotalOrders()). - Arrays genéricos de objetos (
List<Object[]>) para consultas agregadas ad-hoc.
- Proyecciones basadas en interfaces (Interface-based Projections) para mapear alias SQL a getters tipados (
- Consultas Dinámicas con
EntityManager: uso deentityManager.createNativeQuery(sql, Entidad.class)para escenarios que requieren construcción programática de SQL.
Qué debes recordar #
Usa JPQL/JPA para operaciones estándar y @Query(nativeQuery = true) junto con proyecciones e interfaces para consultas agregadas complejas o afinado de rendimiento específico del motor SQL.