Реализовать поисковый фильтр с условиями

Я хочу реализовать функцию поиска с несколькими дополнительными условиями. Я пробовал это:

    @GetMapping("find")
    public Page<PaymentTransactionsDTO> getAllBySpecification(
            @And({
                    @Spec(path = "name", spec = LikeIgnoreCase.class),
                    @Spec(path = "unique_id", spec = LikeIgnoreCase.class),
                    @Spec(path = "createdAt", params = "from", spec = GreaterThanOrEqual.class),
                    @Spec(path = "createdAt", params = "to", spec = LessThanOrEqual.class)
            }) Specification<PaymentTransactions> specification,
            Pageable pageable
    ) {        
        return transactionService.getAllBySpecification(specification, pageable));       
    }

Репозиторий:

      @Override
      public Page<PaymentTransactions> getAllBySpecification(final Specification<PaymentTransactions> specification, final Pageable pageable) {
          return dao.findAll(specification, pageable);
      }

В настоящее время этот запрос работает:

GET /api/transactions/find?unique_id=22&page=0&size=10 

Но также я хочу реализовать эти дополнительные условия поиска, а не только отправку основного поиска для unique_id:

start with 
=
end with 
contains

Используя https://github.com/tkaczmarzyk/specification-arg-разрешитель, есть ли способ отправить дополнительные подусловия? Я не могу найти решение этой проблемы. Каковы наилучшие методы отправки этих значений?

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

Nikolas Charalambidis 30.04.2019 15:28

@Nikolas Спасибо за ответ. Можете ли вы показать мне пример кода, пожалуйста?

Peter Penzov 30.04.2019 15:30

Вы пробовали сначала написать код?

Nikolas Charalambidis 30.04.2019 15:45

См. приведенный выше код, который я пытался реализовать. Я не знаю, как указать спецификацию для вышеуказанного случая.

Peter Penzov 30.04.2019 15:49

Я указал, что не рекомендую вам продолжать использовать аннотации, и предложил реализовать другой механизм. Пожалуйста, прочитайте внимательно.

Nikolas Charalambidis 30.04.2019 15:50

Хорошо, не могли бы вы дать мне пример кода, чтобы ясно увидеть, как правильно его реализовать, пожалуйста?

Peter Penzov 30.04.2019 15:59
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
5
6
1 228
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если вы хотите создать свой особый фильтр, я считаю, что вы должны начать с изобретения интерфейса поиска. Например так:

GET /models?name=eq(john smith)&createdAt=between(2019-01-01,2019-01-31)
GET /models?name=like(sm)&createdAt=from(2019-01-01)
GET /models?name=sw(john)&createdAt=to(2019-01-31)

После этого вы сможете попробовать его реализовать.

IMO лучший способ решить такую ​​задачу - использовать Spring Data JPA Характеристики (и JPA Criteria API). Например:

1) Давайте создадим класс Filter, который реализует Specification для нашей сущности Model:

@Value
public class ModelFilter implements Specification<Model> {

    private String name;
    private String createdAt;

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

        List<Predicate> predicates = new ArrayList<>();

        // Prepare predicates and fill the list with them...

        return builder.and(predicates.toArray(new Predicate[0]));
    }
}

2) Затем создайте метод контроллера:

@GetMapping
public List<Model> getAllByFilter(ModelFilter filter) {
    return repo.findAll(filter); 
}

Осталось подготовить наши предикаты ))

Для этого мы можем сначала создать удобный интерфейс Predicate Builder:

@FunctionalInterface
interface PredicateBuilder<T> {

    Optional<Predicate> get(String fieldName, String value, Root<T> root, CriteriaBuilder builder);

    static Matcher getMatcher(String op, String value) {
        return getMatcher(op, value, "(.+)");
    }

    static Matcher getMatcher(String op, String value, String pattern) {
        return Pattern.compile(op + "\\(" + pattern + "\\)").matcher(value);
    }
}

Затем попробуйте сделать наши предикаты:

равный

PredicateBuilder<Model> eq = (fieldName, value, root, cb) -> {
    Matcher m = getMatcher("eq", value);
    if (m.matches()) {
        return Optional.of(cb.equal(cb.upper(root.get(fieldName)), m.group(1).toUpperCase()));
    } else {
        return Optional.empty();
    }
};

подобно

PredicateBuilder<Model> like = (fn, value, root, cb) -> {
    Matcher m = getMatcher("like", value);
    if (m.matches()) {
        return Optional.of(cb.like(cb.upper(root.get(fn)), "%" + m.group(1).toUpperCase() + "%"));
    } else {
        return Optional.empty();
    }
};

начать с

PredicateBuilder<Model> sw = (fn, value, root, cb) -> {
    Matcher m = getMatcher("sw", value);
    if (m.matches()) {
        return Optional.of(cb.like(cb.upper(root.get(fn)), m.group(1).toUpperCase() + "%"));
    } else {
        return Optional.empty();
    }
};

между

PredicateBuilder<Model> between = (fn, value, root, cb) -> {
    Matcher m = getMatcher("between", value, "(.+)\\s*,\\s*(.+)");
    if (m.matches()) {
        LocalDate from = LocalDate.parse(m.group(1));
        LocalDate to = LocalDate.parse(m.group(2));
        return Optional.of(cb.between(root.get(fn), from, to));
    } else {
        return Optional.empty();
    }
};

от

PredicateBuilder<Model> from = (fn, value, root, cb) -> {
    Matcher m = getMatcher("from", value);
    if (m.matches()) {
        LocalDate from = LocalDate.parse(m.group(1));
        return Optional.of(cb.greaterThanOrEqualTo(root.get(fn), from));
    } else {
        return Optional.empty();
    }
};

к

PredicateBuilder<Model> to = (fn, value, root, cb) -> {
    Matcher m = getMatcher("to", value);
    if (m.matches()) {
        LocalDate to = LocalDate.parse(m.group(1));
        return Optional.of(cb.lessThanOrEqualTo(root.get(fn), to));
    } else {
        return Optional.empty();
    }
};

Осталось только завершить класс Filter:

@Value
public class ModelFilter implements Specification<Model> {

    private String name;
    private String createdAt;

    PredicateBuilder<Model> eq = ... ;
    PredicateBuilder<Model> like = ... ;
    PredicateBuilder<Model> sw = ... ;
    PredicateBuilder<Model> between = ... ;
    PredicateBuilder<Model> from = ... ;
    PredicateBuilder<Model> to = ... ;

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

        List<Predicate> predicates = new ArrayList<>();

        if (name != null) {
            eq.get("name", name, root, builder).ifPresent(predicates::add);
            like.get("name", name, root, builder).ifPresent(predicates::add);
            sw.get("name", name, root, builder).ifPresent(predicates::add);
        }

        if (createdAt != null) {
            between.get("createdAt", createdAt, root, builder).ifPresent(predicates::add);
            from.get("createdAt", createdAt, root, builder).ifPresent(predicates::add);
            to.get("createdAt", createdAt, root, builder).ifPresent(predicates::add);
        }

        return builder.and(predicates.toArray(new Predicate[0]));
    }
}

Конечно, это всего лишь один пример реализации. Вы можете создать свою собственную реализацию спецификаций и предикатов, которые вам нужны. Главное здесь:

  • придумать свой поисковый интерфейс
  • сделайте свой "фильтр" Спецификация
  • подготовьте все необходимые предикаты
  • используйте спецификацию фильтра в методе вашего контроллера

У меня есть вопрос. Знаете ли вы, есть ли другая структура поиска, которая может быть использована для более простой реализации вышеуказанной функциональности?

Peter Penzov 21.05.2019 11:54

Как обычно вы реализуете логику поиска в сложных проектах?

Peter Penzov 21.05.2019 12:02

@PeterPenzov Что ты имеешь в виду?

Cepr0 21.05.2019 12:03

Если у вас есть сложный фильтр поиска для какой-либо таблицы БД, чтобы делать сложные запросы, подобные приведенному выше примеру, что вы используете?

Peter Penzov 21.05.2019 12:04

@PeterPenzov Я думаю, вы можете выбрать любой вариант, который вам нужен: stackoverflow.com/a/55761257

Cepr0 21.05.2019 12:14

У меня есть вопрос. Я попытался расширить спецификацию JPA, но получил NPE: stackoverflow.com/questions/57317139/… Есть идеи, почему?

Peter Penzov 02.08.2019 09:16

Другие вопросы по теме