【发布时间】:2020-06-04 23:37:20
【问题描述】:
我正在研究使用基于光标的分页。我有以下(postgres)表:
CREATE TABLE label (
tenantId VARCHAR(256) NOT NULL,
tagFieldName VARCHAR(256) NOT NULL,
tagId VARCHAR(256) NOT NULL,
"key" VARCHAR(256) NOT NULL,
"value" VARCHAR(256) NOT NULL,
PRIMARY KEY (tenantId, tagFieldName, tagId, "key"),
CONSTRAINT fkTag FOREIGN KEY (tenantId, tagFieldName, tagId) REFERENCES tag (tenantId, fieldName, id)
);
并且已经开始画出分页查询(未测试):
CriteriaBuilder criteriaBuilder = entityManager
.getCriteriaBuilder();
CriteriaQuery<Label> criteriaQuery = criteriaBuilder
.createQuery(Label.class);
Root<Label> root = criteriaQuery.from(Label.class);
final Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("tenantId"), "jim");
final Predicate fieldNamePredicate = criteriaBuilder.equal(root.get("fieldName"), "work");
final Predicate idPredicate = criteriaBuilder.equal(root.get("id"), "work1");
CriteriaQuery<Label> select = criteriaQuery
.select(root).where(criteriaBuilder.and(tenantIdPredicate, fieldNamePredicate, idPredicate));
criteriaQuery.orderBy(
criteriaBuilder.asc(root.get("tenantId")),
criteriaBuilder.asc(root.get("fieldName")),
criteriaBuilder.asc(root.get("id")),
criteriaBuilder.asc(root.get("key")));
TypedQuery<Label> typedQuery = entityManager.createQuery(select);
typedQuery.setMaxResults(100);
List<Label> labels = typedQuery.getResultList();
我一直在阅读这些帖子:
https://slack.engineering/evolving-api-pagination-at-slack-1c1f644f8e12
https://engineering.mixmax.com/blog/api-paging-built-the-right-way/
https://coderwall.com/p/lkcaag/pagination-you-re-probably-doing-it-wrong
两者都声明光标应该是一个唯一的、连续的分页列。
但是,我的表没有有一个自动递增的顺序主键。
相反,它的主键是这四个字段的组合键,它们一起是唯一的,但不是顺序的:
PRIMARY KEY (tenantId, tagFieldName, tagId, "key"),
我能否实现基于光标的分页方法?
还可能允许用户指定一个排序列,例如对“值”列进行排序?
更新:根据@Lesiak 的评论,我相信以下查询可能接近我想要的,请记住tenantId、tagFieldName 和tagId 部分主键是已知的(客户端提供),但主键的 key 字段将未知,假设客户端已要求对 value 字段进行排序。
-- First page
SELECT *
FROM label
WHERE
tenantId = 'jim' AND tagFieldName = 'work' AND tagId = 'work1'
ORDER BY value, tenantId, tagFieldName, tagId, key
LIMIT 2;
-- Next page (assuming the last result of the first query returned a record with the key `label-2-key` and and value `label-2-value`)
SELECT *
FROM label
WHERE
tenantId = 'jim' AND tagFieldName = 'work' AND tagId = 'work1'
AND
(value, key) > ('label-2-value', 'label-2-key')
ORDER BY value, tenantId, tagFieldName, tagId, key
LIMIT 2;
展开第二个(下一页)查询的行值表达式会导致:
SELECT *
FROM label
WHERE
tenantId = 'jim' AND tagFieldName = 'work' AND tagId = 'work1'
AND
value > 'label-2-value' OR ((value = 'label-2-value') AND (key > 'label-2-key'))
ORDER BY value, tenantId, tagFieldName, tagId, key
LIMIT 2;
这将转化为 JPA 查询,例如(未经测试):
CriteriaBuilder criteriaBuilder = entityManager
.getCriteriaBuilder();
CriteriaQuery<Label> criteriaQuery = criteriaBuilder
.createQuery(Label.class);
Root<Label> root = criteriaQuery.from(Label.class);
final Predicate tenantIdEqualPredicate = criteriaBuilder.equal(root.get("tenantId"), tenantId);
final Predicate fieldNameEqualPredicate = criteriaBuilder.equal(root.get("fieldName"), fieldName);
final Predicate idEqualPredicate = criteriaBuilder.equal(root.get("id"), id);
// tenantId = 'jim' AND tagFieldName = 'work' AND tagId = 'work1'
Predicate primaryKeyPredicate = criteriaBuilder.and(tenantIdEqualPredicate, fieldNameEqualPredicate, idEqualPredicate);
final Predicate valueGreaterThanPredicate = criteriaBuilder.greaterThan(root.get("value"), "label-2-value");
final Predicate valueEqualPredicate = criteriaBuilder.equal(root.get("value"), "label-2-value");
final Predicate keyGreaterThanPredicate = criteriaBuilder.equal(root.get("key"), "label-2-key");
// value > 'label-2-value' OR ((value = 'label-2-value') AND (key > 'label-2-key'))
Predicate orderingPredicate = criteriaBuilder.or(
valueGreaterThanPredicate, criteriaBuilder.and(valueEqualPredicate, keyGreaterThanPredicate));
CriteriaQuery<Label> select = criteriaQuery
.select(root).where(
criteriaBuilder.and(primaryKeyPredicate, orderingPredicate));
criteriaQuery.orderBy(
criteriaBuilder.asc(root.get("value")),
criteriaBuilder.asc(root.get("tenantId")),
criteriaBuilder.asc(root.get("fieldName")),
criteriaBuilder.asc(root.get("id")),
criteriaBuilder.asc(root.get("key")));
TypedQuery<Label> typedQuery = entityManager.createQuery(select);
typedQuery.setMaxResults(Defaults.PAGE_SIZE);
List<Label> labels = typedQuery.getResultList();
我对此的问题/疑虑是:
当用户指定多个 对字段进行排序,其中一些可能按 asc/desc 顺序等?
用户还可以为此请求指定过滤器, 例如“标签存在”、“标签键包含”、“标签键以开头” 等等,以及这将如何再次增加构建的复杂性 条件对象?
【问题讨论】:
标签: java sql spring jpa pagination