SuperAPI AI 工具集

data-providers

编程与开发

查看项目

更新于 10-16 03:33

技能介绍

用途

本技能用于指导 AI 智能体在 Vaadin 25 中高效使用数据提供器(Data Providers),应用于 Grid、ComboBox 以及其他列表类组件。当用户就 Vaadin 组件的数据绑定、分页、过滤、排序或刷新提出问题时适用。

在不确定具体 API 细节时,技能要求先使用 Vaadin MCP 工具(search_vaadin_docs、get_component_java_api)查阅最新文档,并将 vaadin_version 设为 "25"、ui_language 设为 "java"。要获取确切的 Java API 签名或阅读源码,使用 javadoc MCP(mcp__javadoc__*)从 Maven Central 读取 Javadoc 与源码,而不是解压 ~/.m2 中的 jar 包。

两种方式:内存加载 vs 懒加载

内存加载

适用于能装入服务器内存的小数据集,例如通过 grid.setItems(people) 直接设置列表。当完整数据集较小(数百到数千条),或出于其他原因已经加载好数据时使用。内存数据通过 ListDataView 支持类似客户端的排序与过滤,例如 dataView.setFilter(...) 和 dataView.setSortOrder(...)。

懒加载

按需加载,只加载可见部分数据,过滤与排序委托给后端(通常是数据库)。

使用 Spring(推荐,使用 Pageable):

grid.setItemsPageable(personService::list);

// Service method signature:
public List<Person> list(Pageable pageable) {
    return repository.findAllBy(pageable).getContent();
}

setItemsPageable 会把 Grid 内部查询转换为带 offset、limit 和排序信息的 Spring Pageable,可直接与 Spring Data 仓库集成。

技能建议使用 Slice 而非 Page:仓库方法应返回 Slice 而不是 Page,因为 Page 会执行额外的 COUNT 查询,而 Grid 无法利用它——Grid 将计数视为独立关注点,通过专门的 count 回调处理。应使用返回 Slice 的仓库方法(如 Slice<Person> findAllBy(Pageable pageable)),并在需要时提供独立的计数方法。

不使用 Spring(通用回调):

grid.setItems(query ->
    personService.fetch(query.getOffset(), query.getLimit()).stream()
);

回调接收的 Query 对象提供 getOffset()、getLimit() 和 getSortOrders()。

过滤

内存过滤:通过 GridListDataView,在过滤字段的监听器中调用 dataView.setFilter(...)。

懒过滤(后端委托):把过滤值传入服务回调。使用 Spring 时通过 grid.setItemsPageable(pageable -> personService.list(pageable, filterField.getValue())) 获取 GridLazyDataView,并将过滤字段设为 ValueChangeMode.LAZY,在值变化时调用 dataView.refreshAll()。不使用 Spring 时,在查询回调中读取过滤值,值变化时调用 grid.getDataProvider().refreshAll()。技能建议在过滤字段上使用 ValueChangeMode.LAZY,等待用户停止输入后再触发,以减少后端调用。

排序

内存排序:Comparable 类型的列自动可排序,可通过 setComparator 自定义比较器。

懒排序(后端委托):通过 setSortableColumns(按属性名)或逐列 setSortable(true) 声明可排序列,然后在回调中处理 query.getSortOrders()。使用 setItemsPageable 时,排序会自动通过 Pageable 对象处理。

ComboBox 懒加载

ComboBox 支持懒加载和内置文本过滤。使用 Spring 时调用 comboBox.setItemsPageable(productService::list),服务方法需接受过滤字符串。不使用 Spring 时,在查询回调中读取 query.getFilter().orElse("")。

提供条目计数

若没有计数回调,Grid 使用无限滚动(滚动条随加载更新)。为获得更好体验,应提供计数:使用 Spring 时调用 grid.setItemsPageable(personService::list, personService::count);不使用 Spring 时向 setItems 传入 fetch 与 count 两个回调。若精确计数代价过高,可提供估算值,例如 dataView.setItemCountEstimate(1000) 和 dataView.setItemCountEstimateIncrease(500)。

更新已显示数据

刷新单个条目:调用 dataView.refreshItem(person) 只更新该行。刷新全部数据:调用 dataView.refreshAll() 重新获取所有内容。内存列表可变更时,可通过 GridListDataView 的 addItem、removeItem 增删条目。

条目身份

组件依赖 hashCode() 和 equals() 判断条目身份,二者必须基于稳定唯一的属性(通常是数据库 ID),而不是可变字段。ID 字段必须是 final(如 private final Long id;),确保构造后哈希码不变;可变 ID 会违反 hashCode() 约定并破坏 Grid 的内部身份跟踪。使用 Lombok 时,可用 @EqualsAndHashCode(onlyExplicitlyIncluded = true) 并在 ID 字段上加 @EqualsAndHashCode.Include。

复用数据绑定逻辑

不要将 Grid 或 DataProvider 交由 Spring 管理——它们持有 UI 状态并绑定到单一用户会话,应作为普通类创建,通过构造函数传入依赖。技能给出两种模式:领域专用的 Grid 子类(如 PersonGrid),以及独立的 DataProvider 类(继承 AbstractBackEndDataProvider,实现 fetchFromBackEnd 与 sizeInBackEnd)。

最佳实践

  • 使用 Spring 时优先用 setItemsPageable,自动完成 offset/limit/sort 到 Pageable 的转换。
  • 数据集超过约 1000 条时使用懒加载;内存加载更简单但不可扩展。
  • 始终实现稳定的 hashCode/equals,基于实体 ID 而非可变字段,这是 Grid 正确跟踪选中与更新的必要条件。
  • 在实体或 DTO 上声明列名常量,避免 setKey()、setSortableColumns() 中字符串键的拼写错误,使重构更安全并支持编译期检查。
  • 过滤字段使用 ValueChangeMode.LAZY,避免用户输入时产生过多后端查询。
  • 尽可能提供计数回调,给用户正确的滚动条和跳转到末尾的能力。
  • 当过滤/排序条件在外部变化时调用 refreshAll(),因为 Grid 不会自动感知过滤字段值的变化。
  • 不要在懒加载数据上调用 getGenericDataView().getItems(),这会把整个数据集载入内存,使懒加载失去意义。