Saltar al contenido principal

Consultando datos 25.02

Abrir en ChatGPT

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);