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 coincidanfindOne(Example<T> example)
: Busca una única entidadexists(Example<T> example)
: Verifica si existe alguna coincidenciacount(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.
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 (soloAND
) - 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