Я хочу реализовать функцию поиска с несколькими дополнительными условиями. Я пробовал это:
@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 Спасибо за ответ. Можете ли вы показать мне пример кода, пожалуйста?
Вы пробовали сначала написать код?
См. приведенный выше код, который я пытался реализовать. Я не знаю, как указать спецификацию для вышеуказанного случая.
Я указал, что не рекомендую вам продолжать использовать аннотации, и предложил реализовать другой механизм. Пожалуйста, прочитайте внимательно.
Хорошо, не могли бы вы дать мне пример кода, чтобы ясно увидеть, как правильно его реализовать, пожалуйста?




Если вы хотите создать свой особый фильтр, я считаю, что вы должны начать с изобретения интерфейса поиска. Например так:
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]));
}
}
Конечно, это всего лишь один пример реализации. Вы можете создать свою собственную реализацию спецификаций и предикатов, которые вам нужны. Главное здесь:
У меня есть вопрос. Знаете ли вы, есть ли другая структура поиска, которая может быть использована для более простой реализации вышеуказанной функциональности?
Как обычно вы реализуете логику поиска в сложных проектах?
@PeterPenzov Что ты имеешь в виду?
Если у вас есть сложный фильтр поиска для какой-либо таблицы БД, чтобы делать сложные запросы, подобные приведенному выше примеру, что вы используете?
@PeterPenzov Я думаю, вы можете выбрать любой вариант, который вам нужен: stackoverflow.com/a/55761257
У меня есть вопрос. Я попытался расширить спецификацию JPA, но получил NPE: stackoverflow.com/questions/57317139/… Есть идеи, почему?
Я рекомендую вам не обрабатывать это на уровне определения метода с помощью аннотаций, а разрешить передачу всех параметров и реализовать валидатор логики или языка выражений для обработки возможных комбинаций.