Спецификация заказа по последнему элементу отношения @OneToMany

Вопросы и ответы

У меня есть сущность Player, которая имеет @OneToMany связь с PlayerTransfer. Я хочу отсортировать игроков по бюджету последнего трансфера.

Вот резюме сущностей:


@Entity
@Getter
@Setter
@NoArgsConstructor
public class Player {

    // другие поля...

    @OneToMany(mappedBy = "playerTransfer")
    @Builder.Default
    private List<PlayerTransfer> playerTransferList = new ArrayList<>();

}

@Entity
@Getter
@Setter
@NoArgsConstructor
public class PlayerTransfer {

    // другие поля

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "player_id")
    private Player player;

    private OffsetDateTime transferTime;
    private Integer transferRate;
    private Integer budget;

}


Попытка-1

Я написал метод спецификации, как показано ниже, но он вызывает ошибку SQL [ERROR: more than one row returned by a subquery used as an expression\], если у игрока более одного трансфера.


public static Specification<Player> orderByBudget(boolean desc) {
        return (root, query, criteriaBuilder) -> {
            Subquery<PlayerTransfer> sQuery = query.subquery(PlayerTransfer.class);
            Root<PlayerTransfer> subRoot = sQuery.from(PlayerTransfer.class);
            Subquery<PlayerTransfer> subquery= sQuery.select(subRoot).where(criteriaBuilder.equal(subRoot.get("player"), root));
            Join<Player, PlayerTransfer> playerTransferJoin = root.join("playerTransferList", JoinType.LEFT);

            Order order;
            if (desc) {
                order = criteriaBuilder.desc(criteriaBuilder.selectCase()
                        .when(criteriaBuilder.isNotNull(playerTransferJoin.get("budget")), criteriaBuilder.selectCase()
                                .when(criteriaBuilder.equal(playerTransferJoin, subquery), playerTransferJoin.get("budget"))));
            } else {
                order = criteriaBuilder.asc(criteriaBuilder.selectCase()
                        .when(criteriaBuilder.isNotNull(playerTransferJoin.get("budget")), criteriaBuilder.selectCase()
                                .when(criteriaBuilder.equal(playerTransferJoin, subquery), playerTransferJoin.get("budget"))));
            }

            List<Order> orders = new ArrayList<>(query.getOrderList());
            orders.add(order);
            query.orderBy(orders);
            return query.getRestriction();
        };
    }

Попытка-2

public static Specification<Player> orderByBudget(boolean desc) {
        return (root, query, criteriaBuilder) -> {
            Subquery<Long> subquery = query.subquery(Long.class);
            Root<PlayerTransfer> statusRoot = subquery.from(PlayerTransfer.class);
            subquery.select(criteriaBuilder.greatest(statusRoot.get("budget").as(Long.class))).where();
            subquery.where(criteriaBuilder.equal(statusRoot.get("player"), root));

            Order order;
            if (desc) {
                order = criteriaBuilder.desc(subquery);
            } else {
                order = criteriaBuilder.asc(subquery);
            }
            List<Order> orders = new ArrayList<>(query.getOrderList());
            orders.add(order);
            query.orderBy(orders);
            return query.getRestriction();
        };
    }

Моя другая попытка представлена выше, этот метод не вызывает никаких исключений, но он не работает так, как я хочу. Поскольку подзапрос выбирает максимальный бюджет и сортирует по нему, я хочу получить бюджет последнего трансфера. Можете ли вы помочь мне с этой проблемой.

В резюме, я хочу отсортировать игроков по бюджету последнего трансфера.

Ответ

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

Ниже приведён пример того, как можно реализовать эту логику:

import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.*;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;

public static Specification<Player> orderByLatestTransferBudget(boolean desc) {
    return (root, query, criteriaBuilder) -> {
        // Указываем, что мы хотим получить только последние трансферы каждого игрока
        Subquery<PlayerTransfer> subquery = query.subquery(PlayerTransfer.class);
        Root<PlayerTransfer> subRoot = subquery.from(PlayerTransfer.class);

        // Подзапрос будет возвращать последний трансфер для каждого игрока
        subquery.select(subRoot)
                .where(criteriaBuilder.equal(subRoot.get("player"), root))
                .orderBy(criteriaBuilder.desc(subRoot.get("transferTime")));

        // Убираем дублирования, чтобы вернуть только последний трансфер
        subquery.distinct(true);

        // Создаём условие на бюджеты последних трансферов
        query.select(root)
                .where(criteriaBuilder.equal(subRoot, subquery))
                .distinct(true);

        // Определяем порядок сортировки по бюджету последнего трансфера
        Expression<Integer> latestTransferBudget = criteriaBuilder.coalesce(
                criteriaBuilder.selectCase()
                        .when(criteriaBuilder.exists(subquery), subRoot.get("budget")),
                0); // Значение по умолчанию, если трансферов нет

        // Добавляем сортировку
        if (desc) {
            query.orderBy(criteriaBuilder.desc(latestTransferBudget));
        } else {
            query.orderBy(criteriaBuilder.asc(latestTransferBudget));
        }

        return query.getRestriction();
    };
}

Объяснение кода:

  1. Подзапрос (Subquery):

    • Мы создаём подзапрос, чтобы получить список последних трансферов для каждого игрока на основе даты transferTime.
    • Условие when(criteriaBuilder.exists(subquery), subRoot.get("budget")) позволяет нам получить бюджет каждого последнего трансфера.
  2. Сортировка:

    • Используем criteriaBuilder.coalesce(...) для обработки случаев, где у игрока может не быть трансферов (значение 0, если трансферов нет).
    • В зависимости от параметра desc, устанавливается сортировка по бюджету последних трансферов.
  3. Возвращаемое значение:
    • Мы возвращаем query.getRestriction() для ограничения выборки на основе принятого условия.

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

Оцените материал
Добавить комментарий

Капча загружается...