API Query By Example (QBE)

Intermedio
SpringBoot
SpringBoot
Actualizado: 13/06/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

Introducción a Query By Example

Query By Example (QBE) es una técnica de consulta que permite crear consultas dinámicas utilizando una instancia de entidad como plantilla. En lugar de escribir consultas JPQL o SQL complejas, QBE nos permite definir criterios de búsqueda simplemente estableciendo valores en los campos de una entidad de ejemplo.

Esta aproximación resulta especialmente útil cuando necesitamos construir consultas flexibles donde los criterios de búsqueda pueden variar según la entrada del usuario. Por ejemplo, en un formulario de búsqueda donde algunos campos pueden estar vacíos y otros completados.

Fundamentos de Query By Example

Spring Data JPA implementa QBE a través de la interfaz QueryByExampleExecutor, que se integra automáticamente con nuestros repositorios. El concepto central gira en torno a tres elementos principales:

  • Probe: La instancia de entidad que contiene los valores de ejemplo
  • ExampleMatcher: Define cómo se deben comparar los campos
  • Example: Combina el probe con el matcher para formar la consulta

Consideremos una entidad Producto básica para ilustrar estos conceptos:

@Entity
public class Producto {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String nombre;
    private String categoria;
    private BigDecimal precio;
    private Boolean activo;
    
    // Constructores, getters y setters
    public Producto() {}
    
    public Producto(String nombre, String categoria, BigDecimal precio) {
        this.nombre = nombre;
        this.categoria = categoria;
        this.precio = precio;
        this.activo = true;
    }
}

Implementación básica en repositorios

Para utilizar QBE, nuestro repositorio debe extender QueryByExampleExecutor además de JpaRepository:

@Repository
public interface ProductoRepository extends JpaRepository<Producto, Long>, 
                                          QueryByExampleExecutor<Producto> {
}

Esta extensión nos proporciona varios métodos útiles para trabajar con ejemplos:

  • findAll(Example<T> example): Busca todas las entidades que coincidan
  • findOne(Example<T> example): Busca una única entidad
  • exists(Example<T> example): Verifica si existe alguna coincidencia
  • count(Example<T> example): Cuenta las coincidencias

Creación de consultas simples

La forma más directa de usar QBE es crear una instancia de nuestra entidad con los valores que queremos buscar:

@Service
public class ProductoService {
    
    @Autowired
    private ProductoRepository productoRepository;
    
    public List<Producto> buscarPorCategoria(String categoria) {
        // Creamos el probe con el valor de búsqueda
        Producto probe = new Producto();
        probe.setCategoria(categoria);
        
        // Creamos el Example y ejecutamos la consulta
        Example<Producto> example = Example.of(probe);
        return productoRepository.findAll(example);
    }
}

En este ejemplo, Spring Data JPA generará automáticamente una consulta que busque todos los productos cuya categoría coincida exactamente con el valor proporcionado. Los campos que no establecemos en el probe son ignorados en la consulta.

Comportamiento por defecto

QBE aplica ciertas reglas por defecto que es importante conocer:

  • Los campos null se ignoran completamente
  • Los campos de tipo String utilizan coincidencia exacta y son case-sensitive
  • Los campos numéricos y booleanos requieren coincidencia exacta
  • Las propiedades anidadas se evalúan recursivamente

Veamos un ejemplo más complejo que demuestra este comportamiento:

public List<Producto> buscarProductos(String nombre, String categoria, Boolean activo) {
    Producto probe = new Producto();
    
    // Solo se incluirán en la consulta los campos no nulos
    if (nombre != null) {
        probe.setNombre(nombre);
    }
    if (categoria != null) {
        probe.setCategoria(categoria);
    }
    if (activo != null) {
        probe.setActivo(activo);
    }
    
    Example<Producto> example = Example.of(probe);
    return productoRepository.findAll(example);
}

Esta implementación genera consultas dinámicas donde solo se incluyen los criterios que tienen valores definidos, proporcionando una flexibilidad considerable sin necesidad de construir consultas JPQL complejas con múltiples condiciones opcionales.

¿Te está gustando esta lección?

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

Creación de Example y matchers

Aunque la funcionalidad básica de QBE es útil, su verdadero potencial se desbloquea cuando personalizamos el comportamiento de las consultas mediante ExampleMatcher. Esta clase nos permite definir reglas específicas sobre cómo se deben comparar los campos, transformando QBE en una herramienta mucho más flexible.

Configuración básica de ExampleMatcher

El ExampleMatcher se configura utilizando un patrón builder que permite encadenar múltiples configuraciones. La configuración más común implica modificar el comportamiento de las cadenas de texto:

@Service
public class ProductoService {
    
    @Autowired
    private ProductoRepository productoRepository;
    
    public List<Producto> buscarProductosFlexible(String nombre, String categoria) {
        Producto probe = new Producto();
        probe.setNombre(nombre);
        probe.setCategoria(categoria);
        
        // Configuramos el matcher para búsquedas más flexibles
        ExampleMatcher matcher = ExampleMatcher.matching()
            .withIgnoreCase() // Ignora mayúsculas y minúsculas
            .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING); // Búsqueda parcial
        
        Example<Producto> example = Example.of(probe, matcher);
        return productoRepository.findAll(example);
    }
}

Esta configuración hace que las búsquedas de texto sean insensibles a mayúsculas y permitan coincidencias parciales, similar a usar LIKE %valor% en SQL.

Tipos de coincidencia para cadenas

Spring Data JPA ofrece varios tipos de coincidencia que podemos aplicar globalmente o a campos específicos:

Configuración global para todos los campos String:

ExampleMatcher matcher = ExampleMatcher.matching()
    .withStringMatcher(ExampleMatcher.StringMatcher.STARTING) // Comienza con
    .withIgnoreCase();

// También disponibles:
// .withStringMatcher(ExampleMatcher.StringMatcher.ENDING)     // Termina con
// .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) // Contiene
// .withStringMatcher(ExampleMatcher.StringMatcher.EXACT)      // Coincidencia exacta

Configuración específica por campo:

public List<Producto> buscarConCriteriosMixtos(String nombre, String categoria) {
    Producto probe = new Producto();
    probe.setNombre(nombre);
    probe.setCategoria(categoria);
    
    ExampleMatcher matcher = ExampleMatcher.matching()
        .withMatcher("nombre", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
        .withMatcher("categoria", ExampleMatcher.GenericPropertyMatchers.exact());
    
    Example<Producto> example = Example.of(probe, matcher);
    return productoRepository.findAll(example);
}

En este ejemplo, el campo nombre permite búsquedas parciales sin distinguir mayúsculas, mientras que categoria requiere coincidencia exacta.

Ignorar campos específicos

Frecuentemente necesitamos excluir ciertos campos de la consulta, incluso cuando tienen valores asignados. Esto es especialmente útil con campos como identificadores o timestamps:

public List<Producto> buscarSinId(Producto productoEjemplo) {
    ExampleMatcher matcher = ExampleMatcher.matching()
        .withIgnorePaths("id", "fechaCreacion") // Excluye estos campos
        .withIgnoreCase()
        .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);
    
    Example<Producto> example = Example.of(productoEjemplo, matcher);
    return productoRepository.findAll(example);
}

Manejo de valores nulos

Por defecto, QBE ignora los campos null, pero podemos modificar este comportamiento para incluir explícitamente campos nulos en nuestras consultas:

public List<Producto> buscarProductosConDescripcionNula() {
    Producto probe = new Producto();
    probe.setDescripcion(null); // Queremos buscar productos sin descripción
    
    ExampleMatcher matcher = ExampleMatcher.matching()
        .withIncludeNullValues() // Incluye campos nulos en la consulta
        .withIgnorePaths("id");
    
    Example<Producto> example = Example.of(probe, matcher);
    return productoRepository.findAll(example);
}

Combinación de múltiples configuraciones

Los matchers realmente brillan cuando combinamos múltiples configuraciones para crear consultas sofisticadas:

public List<Producto> busquedaAvanzada(String nombreParcial, String categoriaExacta, 
                                      BigDecimal precioMinimo) {
    Producto probe = new Producto();
    probe.setNombre(nombreParcial);
    probe.setCategoria(categoriaExacta);
    probe.setPrecio(precioMinimo);
    
    ExampleMatcher matcher = ExampleMatcher.matching()
        .withIgnorePaths("id", "fechaCreacion", "activo")
        .withMatcher("nombre", 
            ExampleMatcher.GenericPropertyMatchers.contains()
                .ignoreCase())
        .withMatcher("categoria", 
            ExampleMatcher.GenericPropertyMatchers.exact())
        .withMatcher("precio", 
            ExampleMatcher.GenericPropertyMatchers.greaterThanOrEqual());
    
    Example<Producto> example = Example.of(probe, matcher);
    return productoRepository.findAll(example);
}

Reutilización de matchers

Para mantener el código limpio y consistente, es recomendable crear matchers reutilizables como métodos privados o constantes:

@Service
public class ProductoService {
    
    private static final ExampleMatcher MATCHER_BUSQUEDA_TEXTO = 
        ExampleMatcher.matching()
            .withIgnoreCase()
            .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING)
            .withIgnorePaths("id", "fechaCreacion");
    
    private static final ExampleMatcher MATCHER_BUSQUEDA_EXACTA = 
        ExampleMatcher.matching()
            .withIgnorePaths("id", "fechaCreacion");
    
    public List<Producto> buscarPorTexto(String nombre, String categoria) {
        Producto probe = new Producto();
        probe.setNombre(nombre);
        probe.setCategoria(categoria);
        
        Example<Producto> example = Example.of(probe, MATCHER_BUSQUEDA_TEXTO);
        return productoRepository.findAll(example);
    }
}

Limitaciones importantes

Es crucial entender que QBE tiene ciertas limitaciones inherentes:

  • No soporta consultas con OR lógico (solo AND)
  • No permite comparaciones complejas como rangos de fechas
  • Las consultas anidadas están limitadas
  • No soporta funciones de agregación

Para casos que excedan estas limitaciones, será necesario combinar QBE con otros métodos de consulta o utilizar @Query con JPQL personalizado.

Aprendizajes de esta lección

  • Comprender el concepto y fundamentos de Query By Example (QBE) en Spring Data JPA.
  • Aprender a implementar QBE mediante la interfaz QueryByExampleExecutor en repositorios.
  • Saber crear consultas dinámicas simples usando instancias de entidad como ejemplo (probe).
  • Configurar y personalizar consultas con ExampleMatcher para búsquedas flexibles y específicas.
  • Reconocer las limitaciones de QBE y cuándo es necesario utilizar otras técnicas de consulta.

Completa SpringBoot y certifícate

Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración