跳至主要内容

查询数据 25.02

在 ChatGPT 中打开

QueryableRepository 接口扩展了 Repository,提供通过 RepositoryCriteria 进行高级查询。与仅支持简单过滤的基本存储库不同,可查询的存储库提供结构化查询,具有自定义过滤器类型、排序和分页。

理解过滤器类型

QueryableRepository 引入了第二个泛型参数作为过滤器类型: QueryableRepository<T, F>,其中 T 是您的实体类型,F 是您的自定义过滤器类型。

这种分离存在的原因是不同的数据源使用不同的查询语言:

// 内存集合的谓词过滤器
QueryableRepository<Product, Predicate<Product>> inMemoryRepo =
new CollectionRepository<>(products);

// REST API 或数据库的自定义过滤器对象
QueryableRepository<User, UserFilter> apiRepo =
new DelegatingRepository<>(/* 实现 */);

// 搜索引擎的字符串查询
QueryableRepository<Document, String> searchRepo =
new DelegatingRepository<>(/* 实现 */);

CollectionRepository 使用 Predicate<Product> 因为它过滤的是内存中的 Java 对象。REST API 存储库使用 UserFilter - 这是一个包含 departmentstatus 等字段的自定义类,这些字段映射到查询参数。搜索存储库使用普通字符串进行全文查询。

UI 组件不关心这些差异。它们用存储库期望的任何过滤器类型调用 setBaseFilter(),存储库处理转换。

使用存储库标准构建查询

RepositoryCriteria 将所有查询参数捆绑成一个不可变对象。您不再需要为过滤器、排序和分页分别调用方法,而是一次性传递所有参数:

// 完整查询,包含所有参数
RepositoryCriteria<Product, Predicate<Product>> criteria =
new RepositoryCriteria<>(
20, // 偏移量 - 跳过前 20 项
10, // 限制 - 获取 10 项
orderCriteria, // 排序规则
product -> product.getPrice() < 100.0 // 过滤条件
);

// 执行查询
Stream<Product> results = repository.findBy(criteria);
int totalMatching = repository.size(criteria);

findBy() 方法执行完整查询 - 它应用过滤器,排序结果,跳过偏移量,并获取限制。size() 方法计算与过滤条件匹配的所有项目,而忽略分页。

您还可以仅创建所需部分的标准:

// 仅过滤
RepositoryCriteria<Product, Predicate<Product>> filterOnly =
new RepositoryCriteria<>(product -> product.isActive());

// 仅分页
RepositoryCriteria<Product, Predicate<Product>> pageOnly =
new RepositoryCriteria<>(0, 25);

使用不同过滤器类型

谓词过滤器

对于内存集合,使用 Predicate<T> 组合函数过滤器:

CollectionRepository<Product> repository = new CollectionRepository<>(products);

// 构建复杂的谓词
Predicate<Product> activeProducts = product -> product.isActive();
Predicate<Product> inStock = product -> product.getStock() > 0;
Predicate<Product> affordable = product -> product.getPrice() < 50.0;

// 合并条件
repository.setBaseFilter(activeProducts.and(inStock).and(affordable));

// 动态过滤
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);

自定义过滤器对象

外部数据源无法执行 Java 谓词。相反,您需要创建表示您的后端可以搜索的内容的过滤器类:

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

// getter 和 setter...
}

// 与自定义存储库一起使用
ProductFilter filter = new ProductFilter();
filter.setCategory("电子产品");
filter.setMaxPrice(new BigDecimal("99.99"));
filter.setInStock(true);

RepositoryCriteria<Product, ProductFilter> criteria =
new RepositoryCriteria<>(filter);

Stream<Product> results = customRepository.findBy(criteria);

在自定义存储库的 findBy() 方法中,您需要转换此过滤器对象:

  • 对于 REST API:转换为查询参数,例如 ?category=电子产品&maxPrice=99.99&inStock=true
  • 对于 SQL:构建类似 WHERE category = ? AND price <= ? AND stock > 0 的 WHERE 子句
  • 对于 GraphQL:构造具有适当字段选择的查询

Repository 实现应处理此转换,使您的 UI 代码保持干净。

排序数据

OrderCriteria 定义了如何对数据进行排序。每个 OrderCriteria 需要一个值提供者(如何从实体中获取值)和一个方向:

// 单字段排序
OrderCriteria<Employee, String> byName =
new OrderCriteria<>(Employee::getName, OrderCriteria.Direction.ASC);

// 多级排序 - 部门优先,接着是工资,然后是姓名
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));

// 在标准中使用
RepositoryCriteria<Employee, Predicate<Employee>> criteria =
new RepositoryCriteria<>(0, 50, sorting, employee -> employee.isActive());

值提供者(Employee::getName)适用于内存排序。但是外部数据源无法执行 Java 函数。在这些情况下,OrderCriteria 接受属性名称:

// 对于外部存储库 - 提供值获取器和属性名称
OrderCriteria<Employee, String> byName = new OrderCriteria<>(
Employee::getName, // 用于内存排序
Direction.ASC,
null, // 自定义比较器(可选)
"name" // 后端排序的属性名称
);

CollectionRepository 使用值提供者进行 Java 对象排序。DelegatingRepository 实现可以使用属性名称在 SQL 中构建排序子句或在 REST API 中构建 sort=name:asc

控制分页

设置偏移量和限制以控制加载的数据切片:

// 基于页的分页
int page = 2; // 从零开始的页码
int pageSize = 20; // 每页项数
int offset = page * pageSize;

RepositoryCriteria<Product, Predicate<Product>> criteria =
new RepositoryCriteria<>(offset, pageSize, null, yourFilter);

// 逐步加载 - 逐步加载更多数据
int currentlyLoaded = 50;
int loadMore = 25;

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