Я исследую использование пагинации на основе курсора. У меня есть следующая (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 / des c order et c?
Пользователь также сможет указать фильтры для этого запроса, например, «метка существует», «ключ метки содержит», «ключ метки начинается с« et c », и как это снова увеличит сложность построения критерии критериев?