Разбивка на основе курсора для таблицы без уникального и последовательного столбца id? - PullRequest
0 голосов
/ 20 февраля 2020

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

1 Ответ

1 голос
/ 20 февраля 2020

Да, вы можете реализовать нумерацию клавиш с помощью составного ключа. Но JPA не является идеальным инструментом для этого - вам придется создавать свои предикаты.

Подробности:

Давайте изменим пример из среды, которая изначально поддерживает набор ключей нумерация страниц: https://www.jooq.org/doc/latest/manual/sql-building/sql-statements/select-statement/seek-clause/

SELECT id1, id2, value
FROM t
WHERE (value, id1, id2) > (2, 533, 444)
ORDER BY value, id1, id2
LIMIT 5

Этот тип запроса вы хотите сгенерировать в своей реализации. Обратите внимание, что сначала заказы производятся по выбранному пользователем полю, а затем по всем частям составного идентификатора.

Проблема заключается в том, что JPA не поддерживает предикаты выражения строки (и только некоторые базы данных поддерживают их). Вы должны развернуть их самостоятельно:

(A, B) > (X, Y)

становится:

(A > X)
  OR ((A = X) AND (B > Y))

Поскольку в вашем ПК 4 поля, полученный предикат будет довольно длинным.

...