跳至主要内容

自定义数据源 25.02

在 ChatGPT 中打开

当您的数据存在于应用程序之外—在 REST API、数据库或外部服务中—您需要创建自定义存储库实现。DelegatingRepository 类通过允许您提供函数而不是实现一个完整的类,使这一过程变得简单。

DelegatingRepository 的工作原理

DelegatingRepository 是一个具体类,扩展了 AbstractQueryableRepository。您不需要实现抽象方法,而是提供三个函数作为构造函数的参数:

DelegatingRepository<User, UserFilter> repository = new DelegatingRepository<>(
// 1. 查找函数 - 返回过滤/排序/分页的数据
criteria -> userService.findUsers(criteria),

// 2. 计数函数 - 返回过滤条件的总数
criteria -> userService.countUsers(criteria),

// 3. 按键查找函数 - 根据 ID 返回单个实体
userId -> userService.findById(userId)
);

每个函数都有特定的用途:

查找函数接收一个 RepositoryCriteria 对象,其中包含:

  • getFilter() - 您的自定义过滤器对象(F 类型参数)
  • getOffset()getLimit() - 用于分页
  • getOrderCriteria() - 排序规则列表

此函数必须返回一个 Stream<T>,包含匹配条件的实体。如果没有找到匹配项,则流可以为空。

计数函数同样接收条件,但通常只使用过滤器部分。它返回匹配实体的总数,忽略分页。UI 组件使用此信息来显示总结果或计算页数。

按键查找函数接收一个实体键(通常是 ID)并返回一个 Optional<T>。如果实体不存在,则返回 Optional.empty()

REST API 示例

在与 REST API 集成时,您需要将存储库条件转换为 HTTP 请求参数。首先定义一个匹配您 API 查询能力的过滤器类:

public class UserFilter {
private String department;
private String status;
// getters and setters...
}

该过滤器类表示您的 API 接受的搜索参数。当应用过滤时,存储库将把此类的实例传递给您的函数。

创建存储库,使用将条件翻译为 API 调用的函数:

DelegatingRepository<User, UserFilter> apiRepository = new DelegatingRepository<>(
// 查找用户
criteria -> {
Map<String, String> params = buildParams(criteria);
List<User> users = restClient.get("/users", params);
return users.stream();
},

// 计数用户
criteria -> {
Map<String, String> filterParams = buildFilterParams(criteria.getFilter());
return restClient.getCount("/users/count", filterParams);
},

// 按 ID 查找
userId -> restClient.getById("/users/" + userId)
);

buildParams() 方法将从条件中提取值并将其转换为查询参数,例如 ?department=Sales&status=active&offset=20&limit=10。您的 REST 客户端随后会发出实际 HTTP 请求并反序列化响应。

数据库示例

数据库集成遵循类似的模式,但将条件转换为 SQL 查询。关键区别在于处理 SQL 生成和参数绑定:

DelegatingRepository<Customer, CustomerFilter> dbRepository = new DelegatingRepository<>(
// 使用过滤器、排序、分页的查询
criteria -> {
String sql = buildQuery(criteria);
return jdbcTemplate.queryForStream(sql, rowMapper);
},

// 计数匹配记录
criteria -> {
String countSql = buildCountQuery(criteria.getFilter());
return jdbcTemplate.queryForObject(countSql, Integer.class);
},

// 按主键查找
customerId -> {
String sql = "SELECT * FROM customers WHERE id = ?";
return jdbcTemplate.queryForObject(sql, rowMapper, customerId);
}
);

buildQuery() 方法将构造 SQL,例如:

SELECT * FROM customers 
WHERE status = ? AND region = ?
ORDER BY created_date DESC, name ASC
LIMIT ? OFFSET ?

您的过滤器对象属性映射到 WHERE 子句条件,而分页和排序通过 LIMIT/OFFSETORDER BY 子句进行处理。

与 UI 组件一起使用

存储库模式的美妙之处在于 UI 组件不知道或关心数据来自哪里。无论是内存中的集合、REST API 还是数据库,使用方式都是一样的:

// 创建并配置存储库
Repository<User> repository = createApiRepository();
UserFilter filter = new UserFilter();
filter.setDepartment("Engineering");
repository.setBaseFilter(filter);

// 附加到表格
Table<User> table = new Table<>();
table.setRepository(repository);

// 表格会自动显示过滤后的工程师用户

当用户与 Table 进行交互(对列进行排序、改变页面)时,Table 会使用更新的条件调用您的存储库函数。您的函数将这些条件转换为 API 调用或 SQL 查询,并且表格会自动更新结果。

何时扩展 AbstractQueryableRepository

如果您需要自定义方法或复杂初始化,请直接扩展 AbstractQueryableRepository

public class CustomUserRepository extends AbstractQueryableRepository<User, UserFilter> {
@Override
public Stream<User> findBy(RepositoryCriteria<User, UserFilter> criteria) {
// 实现
}

@Override
public int size(RepositoryCriteria<User, UserFilter> criteria) {
// 实现
}

@Override
public Optional<User> find(Object key) {
// 实现
}

// 添加自定义方法
public List<User> findActiveManagers() {
// 自定义查询逻辑
}
}