Cuando trabajamos con Spring Boot y bases de datos, una de las herramientas más utilizadas es JPA (Java Persistence API), que nos permite interactuar con la base de datos de forma eficiente y sencilla. Sin embargo, en muchas ocasiones, las consultas generadas automáticamente por Spring Data JPA no son suficientes, y necesitamos construir consultas personalizadas.
Para ello, JPA nos proporciona dos mecanismos clave:
JPQL (Java Persistence Query Language) es un lenguaje de consultas basado en objetos que permite realizar búsquedas sobre entidades JPA en lugar de tablas de base de datos. La principal diferencia con SQL es que JPQL trabaja con nombres de entidades y atributos en lugar de nombres de tablas y columnas, lo que lo hace más portable y orientado a objetos.
SELECT
, WHERE
, JOIN
, ORDER BY
, GROUP BY
, etc.
@Query
.
Para realizar consultas personalizadas con JPQL en Spring Boot, podemos utilizar la anotación @Query
en los repositorios de JPA.
Supongamos que tenemos una entidad Usuario
:
@Entity
public class Usuario {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nombre;
private String email;
private int edad;
// Getters y Setters
}
@Repository
public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
// Buscar usuarios por nombre
@Query("SELECT u FROM Usuario u WHERE u.nombre = ?1")
List<Usuario> buscarPorNombre(String nombre);
// Buscar usuarios mayores de cierta edad
@Query("SELECT u FROM Usuario u WHERE u.edad > :edad")
List<Usuario> buscarUsuariosMayores(@Param("edad") int edad);
}
JPQL permite realizar consultas sobre relaciones entre entidades utilizando JOIN.
Por ejemplo, si tenemos una relación entre Usuario
y Pedido
:
@Entity
public class Pedido {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String descripcion;
@ManyToOne
@JoinColumn(name = "usuario_id")
private Usuario usuario;
}
Podemos realizar consultas con JOIN en JPQL:
@Query("SELECT p FROM Pedido p JOIN p.usuario u WHERE u.nombre = :nombre")
List<Pedido> buscarPedidosPorNombreUsuario(@Param("nombre") String nombre);
JPQL admite dos tipos de parámetros:
1. Parámetros posicionales: Se identifican con ?1
, ?2
, etc.
2. Parámetros con nombre: Se identifican con :nombre
, :edad
, etc.
Ejemplo de ambos enfoques:
@Query("SELECT u FROM Usuario u WHERE u.nombre = ?1 AND u.edad = ?2")
List<Usuario> buscarPorNombreYEdad(String nombre, int edad);
@Query("SELECT u FROM Usuario u WHERE u.email = :email")
Usuario buscarPorEmail(@Param("email") String email);
La Criteria API es una herramienta más flexible y poderosa que permite construir consultas de forma dinámica mediante código Java en lugar de escribir directamente JPQL. Esto es especialmente útil cuando:
Para utilizar Criteria API, necesitamos el objeto EntityManager
, el cual nos permite interactuar con la base de datos directamente.
@Service
public class UsuarioService {
@PersistenceContext
private EntityManager entityManager;
public List<Usuario> buscarUsuariosPorEdadMayorA(int edadMinima) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Usuario> query = criteriaBuilder.createQuery(Usuario.class);
Root<Usuario> root = query.from(Usuario.class);
// Condición WHERE u.edad > edadMinima
query.select(root).where(criteriaBuilder.gt(root.get("edad"), edadMinima));
return entityManager.createQuery(query).getResultList();
}
}
La Criteria API es útil para construir consultas dinámicas según condiciones opcionales. Por ejemplo:
public List<Usuario> buscarUsuariosDinamicamente(String nombre, Integer edad) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Usuario> query = cb.createQuery(Usuario.class);
Root<Usuario> root = query.from(Usuario.class);
List<Predicate> predicates = new ArrayList<>();
if (nombre != null) {
predicates.add(cb.equal(root.get("nombre"), nombre));
}
if (edad != null) {
predicates.add(cb.greaterThan(root.get("edad"), edad));
}
query.select(root).where(cb.and(predicates.toArray(new Predicate[0])));
return entityManager.createQuery(query).getResultList();
}
En este ejemplo, si nombre
o edad
son nulos, no se incluirán en la consulta, lo que permite una consulta flexible.
Podemos realizar joins entre entidades de manera similar a JPQL.
public List<Pedido> buscarPedidosPorUsuario(String nombreUsuario) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Pedido> query = cb.createQuery(Pedido.class);
Root<Pedido> pedido = query.from(Pedido.class);
Join<Pedido, Usuario> usuario = pedido.join("usuario");
query.select(pedido).where(cb.equal(usuario.get("nombre"), nombreUsuario));
return entityManager.createQuery(query).getResultList();
}
Característica | JPQL | Criteria API |
---|---|---|
Facilidad de uso | Más simple y fácil de leer. | Más complejo, basado en código. |
Flexibilidad | Menos flexible, consultas fijas. | Muy flexible, consultas dinámicas. |
Mantenibilidad | Más difícil de mantener si hay cambios. | Más fácil de modificar. |
Seguridad | Moderada. | Alta, evita inyecciones SQL. |
Si necesitas consultas simples y predefinidas, JPQL es una mejor opción. Si requieres consultas dinámicas y complejas, Criteria API es la elección correcta.
Spring Boot nos proporciona herramientas poderosas para realizar consultas personalizadas con JPQL y Criteria API, cada una con sus ventajas y casos de uso específicos. Elegir entre una u otra dependerá de los requerimientos de la aplicación y la flexibilidad deseada.
Jorge García
Fullstack developer