Передать один экземпляр Join нескольким экземплярам спецификации? - PullRequest
0 голосов
/ 25 марта 2020

Связано: Подсказка HINT_PASS_DISTINCT_THROUGH уменьшает количество сущностей, возвращаемых на страницу для запроса страницы, до значения ниже настроенного размера страницы (PostgreSQL)

Я настраиваю спецификацию JPA Реализация репозитория на основе, которая использует спецификации jpa (построенные на основе строк фильтра R SQL) для фильтрации результатов, определения порядка следования результатов и удаления любых дубликатов через «различные», которые в противном случае были бы возвращены из-за соединенных таблиц. Метод построителя спецификаций JPA объединяет несколько таблиц и устанавливает «отдельный» флаг:

public final class MySpec implements Specification<Tag>
{
    @Override
    public Predicate toPredicate(
        final Root<Tag> root,
        final CriteriaQuery<?> query,
        final CriteriaBuilder builder)
    {

        final Join<Tag, Label> labelsJoin = root.join("labels", JoinType.INNER);
        final Join<Label, LabelIdentity> labelIdentityJoin = labelsJoin.join("labelIdentity", JoinType.INNER);
        final Predicate labelKeyPredicate = builder.equal(labelIdentityJoin.get("key"), property);

        query.distinct(true);

        return builder.and(
                    labelKeyPredicate,
                    builder.like(labelsJoin.get("value"), argument.replace('*', '%')));
    }
}

Чтобы разрешить сортировку по столбцам объединенной таблицы, я применил подсказку «HINT_PASS_DISTINCT_THROUGH» к соответствующему методу репозитория (в противном случае, сортировка по столбцам объединенной таблицы возвращает ошибку по строкам «столбец сортировки должен быть включен в запрос SELECT DISTINCT»).

После этих изменений кажется, что фильтрация и сортировка работают должным образом. Тем не менее, подсказка, по-видимому, приводит к тому, что «отдельная» фильтрация применяется после того, как страница результатов уже построена, что уменьшает число возвращаемых объектов на странице с настроенного аргумента «Размер» PageRequest до того, что остается после того, как дубликаты отфильтрованы out.

Мой вопрос таков:

Можно ли устранить необходимость использовать разные (и, таким образом, решить проблему подкачки), каким-то образом повторно используя экземпляры Join среди разных экземпляров Specification ? Например, создать экземпляры Join и передать один и тот же экземпляр Join в каждый новый экземпляр спецификации (например, через конструктор)?

Например, я попытался создать что-то вроде следующего, и затем передал этот экземпляр JoinCache в каждый экземпляр Specification, однако я получил ошибки о неправильном псевдониме, поэтому не уверен, что что-то подобное поддерживается даже?

public class JoinCache
{
    private final CriteriaBuilder criteriaBuilder;

    private final CriteriaQuery<Tag> criteriaQuery;

    private final Root<Tag> tagRoot;

    private final Join<Tag, Label> labelJoin;

    private final Join<Label, LabelIdentity> labelIdentityJoin;

    public JoinCache(final CriteriaBuilder criteriaBuilder)
    {
        this.criteriaBuilder = criteriaBuilder;
        this.criteriaQuery = this.criteriaBuilder.createQuery(Tag.class);
        this.tagRoot = criteriaQuery.from(Tag.class);
        this.labelJoin = tagRoot.join("labels", JoinType.INNER);
        this.labelIdentityJoin = labelJoin.join("labelIdentity", JoinType.INNER);
    }

    public Join<Tag, Label> getLabelJoin()
    {
        return labelJoin;
    }

    public Join<Label, LabelIdentity> getLabelIdentityJoin()
    {
        return labelIdentityJoin;
    }

    public CriteriaBuilder getCriteriaBuilder()
    {
        return criteriaBuilder;
    }

    public CriteriaQuery<Tag> getCriteriaQuery()
    {
        return criteriaQuery;
    }

    public Root<Tag> getTagRoot()
    {
        return tagRoot;
    } 
}

Обновление

Альтернативный подход с использованием подзапросов вместо объединений (таким образом, избегая необходимости использовать отличные вообще), однако я считаю, что порядок / сортировка в подзапросах не поддерживаются в спецификациях JPA:

https://hibernate.atlassian.net/browse/HHH-256

public class MySpec implements Specification<Tag>
{
    @Override
    public Predicate toPredicate(
        final Root<Tag> root,
        final CriteriaQuery<?> query,
        final CriteriaBuilder builder)
    {
        final String argument = arguments.get(0);

        final Subquery<Label> subQuery = query.subquery(Label.class);

        final Root<Label> subRoot = subQuery.from(Label.class);

        final Predicate tagPredicate = builder.equal(subRoot.get("tag"), root);

        final Predicate labelKeyPredicate = builder.equal(subRoot.get("labelIdentity").get("key"), "owner");

        subQuery.select(subRoot).where(tagPredicate, labelKeyPredicate, builder.like(subRoot.get("value"), argument.replace('*', '%'));

        return builder.exists(subQuery);
    }
}

1 Ответ

1 голос
/ 26 марта 2020

Неправильно создавать многократно используемые предикаты с побочными эффектами на внешний запрос (я имею в виду query.distinct(true)). Вы можете достичь того же результата, используя предикаты subquery и exists.

Предположим, у Tag объекта есть @Id Long id поле

public final class MySpec implements Specification<Tag> {

    @Override
    public Predicate toPredicate(
        final Root<Tag> root,
        final CriteriaQuery<?> query,
        final CriteriaBuilder builder) {

        Subquery<Long> subquery = query.subquery(Long.class); // if entity id has Long type
        Root<Tag> subRoot = subquery.from(Tag.class);

        final Join<Tag, Label> label = subRoot.join("labels", JoinType.INNER);
        final Join<Label, LabelIdentity> labelIdentity = label.join("labelIdentity", JoinType.INNER);

        final Predicate externalQueryJoinPredicate =
            builder.equal(subRoot.get("id"), root.get("id"));
        final Predicate labelKeyPredicate = 
            builder.equal(labelIdentity.get("key"), property);
        final Predicate labelValuePredicate = 
            builder.like(label.get("value"), argument.replace('*', '%'));

        subquery.select(subRoot.get("id")).where( 
            externalQueryJoinPredicate, labelKeyPredicate, labelValuePredicate);

        return builder.exists(subquery);
     }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...