Consultando datos 25.02
La interfaz QueryableRepository
extiende Repository
con consultas avanzadas a través de RepositoryCriteria
. A diferencia de los repositorios básicos que solo admiten filtrado simple, los repositorios consultables proporcionan consultas estructuradas con tipos de filtro personalizados, ordenamiento y paginación.
Comprendiendo los tipos de filtro
QueryableRepository
introduce un segundo parámetro genérico para el tipo de filtro: QueryableRepository<T, F>
donde T
es tu tipo de entidad y F
es tu tipo de filtro personalizado.
Esta separación existe porque diferentes fuentes de datos hablan diferentes lenguajes de consulta:
// Filtros Predicate para colecciones en memoria
QueryableRepository<Product, Predicate<Product>> inMemoryRepo =
new CollectionRepository<>(products);
// Objetos de filtro personalizados para APIs REST o bases de datos
QueryableRepository<User, UserFilter> apiRepo =
new DelegatingRepository<>(/* implementación */);
// Consultas de cadena para motores de búsqueda
QueryableRepository<Document, String> searchRepo =
new DelegatingRepository<>(/* implementación */);
CollectionRepository
utiliza Predicate<Product>
porque filtra objetos Java en memoria. El repositorio de la API REST utiliza UserFilter
- una clase personalizada con campos como department
y status
que se mapean a parámetros de consulta. El repositorio de búsqueda utiliza cadenas simples para consultas de texto completo.
Los componentes de la interfaz de usuario no se preocupan por estas diferencias. Llaman a setBaseFilter()
con el tipo de filtro que el repositorio espera, y el repositorio maneja la traducción.
Construyendo consultas con criterios de repositorio
RepositoryCriteria
agrupa todos los parámetros de consulta en un objeto inmutable. En lugar de llamar a métodos separados para filtrar, ordenar y paginar, pasas todo de una vez:
// Consulta completa con todos los parámetros
RepositoryCriteria<Product, Predicate<Product>> criteria =
new RepositoryCriteria<>(
20, // offset - saltar los primeros 20
10, // limit - tomar 10 elementos
orderCriteria, // reglas de ordenamiento
product -> product.getPrice() < 100.0 // condición de filtro
);
// Ejecutar la consulta
Stream<Product> results = repository.findBy(criteria);
int totalMatching = repository.size(criteria);
El método findBy()
ejecuta la consulta completa - aplica el filtro, ordena los resultados, salta el offset y toma el límite. El método size()
cuenta todos los elementos que coinciden con el filtro, ignorando la paginación.
También puedes crear criterios solo con las partes que necesitas:
// Solo filtro
RepositoryCriteria<Product, Predicate<Product>> filterOnly =
new RepositoryCriteria<>(product -> product.isActive());
// Solo paginación
RepositoryCriteria<Product, Predicate<Product>> pageOnly =
new RepositoryCriteria<>(0, 25);
Trabajando con diferentes tipos de filtro
Filtros Predicate
Para colecciones en memoria, usa Predicate<T>
para componer filtros funcionales:
CollectionRepository<Product> repository = new CollectionRepository<>(products);
// Construir predicados complejos
Predicate<Product> activeProducts = product -> product.isActive();
Predicate<Product> inStock = product -> product.getStock() > 0;
Predicate<Product> affordable = product -> product.getPrice() < 50.0;
// Combinar condiciones
repository.setBaseFilter(activeProducts.and(inStock).and(affordable));
// Filtrado dinámico
Predicate<Product> filter = product -> true;
if (categoryFilter != null) {
filter = filter.and(p -> p.getCategory().equals(categoryFilter));
}
if (maxPrice != null) {
filter = filter.and(p -> p.getPrice() <= maxPrice);
}
repository.setBaseFilter(filter);
Objetos de filtro personalizados
Las fuentes de datos externas no pueden ejecutar predicados de Java. En su lugar, creas clases de filtro que representan lo que tu backend puede buscar:
public class ProductFilter {
private String category;
private BigDecimal maxPrice;
private Boolean inStock;
// getters y setters...
}
// Usar con el repositorio personalizado
ProductFilter filter = new ProductFilter();
filter.setCategory("Electronics");
filter.setMaxPrice(new BigDecimal("99.99"));
filter.setInStock(true);
RepositoryCriteria<Product, ProductFilter> criteria =
new RepositoryCriteria<>(filter);
Stream<Product> results = customRepository.findBy(criteria);
Dentro del método findBy()
de tu repositorio personalizado, traducirías este objeto de filtro:
- Para APIs REST: Convierte a parámetros de consulta como
?category=Electronics&maxPrice=99.99&inStock=true
- Para SQL: Construye una cláusula where como
WHERE category = ? AND price <= ? AND stock > 0
- Para GraphQL: Construye una consulta con las selecciones de campos apropiadas
La implementación del Repository
debería manejar esta traducción, manteniendo tu código de interfaz de usuario limpio.
Ordenando datos
OrderCriteria
define cómo ordenar tus datos. Cada OrderCriteria
necesita un proveedor de valores (cómo obtener el valor de tu entidad) y una dirección:
// Ordenamiento por un solo campo
OrderCriteria<Employee, String> byName =
new OrderCriteria<>(Employee::getName, OrderCriteria.Direction.ASC);
// Ordenamiento multinivel - departamento primero, luego salario, luego nombre
OrderCriteriaList<Employee> sorting = new OrderCriteriaList<>();
sorting.add(new OrderCriteria<>(Employee::getDepartment, OrderCriteria.Direction.ASC));
sorting.add(new OrderCriteria<>(Employee::getSalary, OrderCriteria.Direction.DESC));
sorting.add(new OrderCriteria<>(Employee::getName, OrderCriteria.Direction.ASC));
// Usar en criterios
RepositoryCriteria<Employee, Predicate<Employee>> criteria =
new RepositoryCriteria<>(0, 50, sorting, employee -> employee.isActive());
El proveedor de valores (Employee::getName
) funciona para ordenamiento en memoria. Pero las fuentes de datos externas no pueden ejecutar funciones de Java. Para esos casos, OrderCriteria acepta un nombre de propiedad:
// Para repositorios externos - proporcionar tanto el getter de valor como el nombre de la propiedad
OrderCriteria<Employee, String> byName = new OrderCriteria<>(
Employee::getName, // Para ordenamiento en memoria
Direction.ASC,
null, // Comparador personalizado (opcional)
"name" // Nombre de la propiedad para ordenamiento en backend
);
CollectionRepository
utiliza el proveedor de valores para ordenar objetos de Java. Las implementaciones de DelegatingRepository
pueden usar el nombre de la propiedad para construir cláusulas de orden en SQL o sort=name:asc
en APIs REST.
Controlando la paginación
Establece offset y limit para controlar qué porción de datos cargar:
// Paginación basada en página
int page = 2; // número de página basado en cero
int pageSize = 20; // elementos por página
int offset = page * pageSize;
RepositoryCriteria<Product, Predicate<Product>> criteria =
new RepositoryCriteria<>(offset, pageSize, null, yourFilter);
// Carga progresiva - cargar más datos incrementalmente
int currentlyLoaded = 50;
int loadMore = 25;
repository.setOffset(0);
repository.setLimit(currentlyLoaded + loadMore);