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 filtros personalizados, ordenación y paginación.

Entendiendo los tipos de filtros​

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 de Predicate para colecciones en memoria
QueryableRepository<Product, Predicate<Product>> inMemoryRepo =
new CollectionRepository<>(products);

// Objetos de filtro personalizados para API REST o bases de datos
QueryableRepository<User, UserFilter> apiRepo =
new DelegatingRepository<>(/* implementación */);

// Consultas de cadenas 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 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 UI no se preocupan por estas diferencias. Llaman a setBaseFilter() con el tipo de filtro que el repositorio espera, y el repositorio se encarga de 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, // desplazamiento - omitir los primeros 20
10, // límite - 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, omite el desplazamiento 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 filtros​

Filtros Predicate​

Para colecciones en memoria, utiliza 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, debes crear clases de filtro que representen lo que tu backend puede buscar:

public class ProductFilter {
private String category;
private BigDecimal maxPrice;
private Boolean inStock;

// getters y setters...
}

// Uso con repositorio personalizado
ProductFilter filter = new ProductFilter();
filter.setCategory("Electrónica");
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 API 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 campo apropiadas

La implementación de Repository debe manejar esta traducción, manteniendo tu código de UI 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:

// Ordenación por campo único
OrderCriteria<Employee, String> byName =
new OrderCriteria<>(Employee::getName, OrderCriteria.Direction.ASC);

// Ordenación multi-nivel - 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));

// Uso en criterios
RepositoryCriteria<Employee, Predicate<Employee>> criteria =
new RepositoryCriteria<>(0, 50, sorting, employee -> employee.isActive());

El proveedor de valores (Employee::getName) funciona para ordenación 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 ordenación en memoria
Direction.ASC,
null, // Comparador personalizado (opcional)
"name" // Nombre de propiedad para ordenación en el backend
);

CollectionRepository utiliza el proveedor de valores para ordenar objetos 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 API REST.

Controlando la paginación​

Establece desplazamiento y límite para controlar qué porción de datos cargar:

// Paginación basada en páginas
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 de forma incremental
int currentlyLoaded = 50;
int loadMore = 25;

repository.setOffset(0);
repository.setLimit(currentlyLoaded + loadMore);