В данной статье мы сделаем фильтрацию выборки из базы по фильтру из json. Для понимания статьи желательно быть знакомыми со Spring репозиторями.
Для фильтрации записей по нескольким условиям предназначена Spring Specification. Specification появилась из концепта представленного в книге Eric Evans Domain Driven Design. Для фильтрации записей в метод findAll передаются предикаты.
Предположим у нас есть подобный json представляющий фильтр.
Подобный фильтр формируют многие js фреимворки, так-что это реальная задача.
Модель json фильтра в java содержит коллекцию из условий и выглядит следующим образом:
Как вы заметили мы реализовали интерфейс Specification, благодаря этому мы можем передать фильтр в метод поиска.
В методе toPredicate() мы должны вернуть Predicate. Каждое условие фильтра это и есть предикат, а коллекцию предикатов можно получить в методе toPredicate(). Поэтому мы перебираем каждое условие и создаем для него предикат а далее накладываем все предикаты друг на друга (добавляем условие «и» выполняем поиск и по году и по цене). Ниже представлен метод buildPredicates():
Для тестирования и, возможно, для дополнения условий фильтра на серверной части создан билдер. Данный фильтр идеально подходит при работе с REST сервисами. Мы дополнили crud репозитории фильтром который может формироваться из json благодаря чему в добавок к crud методам стала возможна динамическая фильтрация просто создав интерфейс репозитория.
С исходным кодом можно ознакомиться тут: https://github.com/VovaOne/spring-data-specification-accurate-filter
Для фильтрации записей по нескольким условиям предназначена Spring Specification. Specification появилась из концепта представленного в книге Eric Evans Domain Driven Design. Для фильтрации записей в метод findAll передаются предикаты.
findAll(where(custoerHasBirthday()).and(isLongTermCustomer()));
Начинаем делать динамический фильтр
Предположим у нас есть подобный json представляющий фильтр.
[{
"type": "string",
"value": "***",
"field": "model"
},{
"type": "numeric",
"value": "***",
"field": "year",
"comparison": "gt"
}]
Подобный фильтр формируют многие js фреимворки, так-что это реальная задача.
В данном примере мы будем фильтровать список с машинками.
@Entity class Car {...}
Модель json фильтра в java содержит коллекцию из условий и выглядит следующим образом:
class Filter implements Specification {
List<Condition> conditions;
@Override
public Predicate toPredicate(/**...*/) {}
}
Как вы заметили мы реализовали интерфейс Specification, благодаря этому мы можем передать фильтр в метод поиска.
В методе toPredicate() мы должны вернуть Predicate. Каждое условие фильтра это и есть предикат, а коллекцию предикатов можно получить в методе toPredicate(). Поэтому мы перебираем каждое условие и создаем для него предикат а далее накладываем все предикаты друг на друга (добавляем условие «и» выполняем поиск и по году и по цене). Ниже представлен метод buildPredicates():
Создание Предикатов
private List<Predicate> buildPredicates(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
conditions.forEach(condition -> predicates.add(buildPredicate(condition, root, criteriaQuery, criteriaBuilder)));
return predicates;
}
public Predicate buildPredicate(Condition condition, Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
switch (condition.comparison) {
case eq:
return buildEqualsPredicateToCriteria(condition, root, criteriaQuery, criteriaBuilder);
case gt:
break;
case lt:
break;
}
throw new RuntimeException();
}
private Predicate buildEqualsPredicateToCriteria(Condition condition, Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.equal(root.get(condition.field), condition.value);
}
Фильтр готов, осталось его протестировать и посмотреть на результат
@Test
public void retrieveCarByFilter() {
Filter filter = new Filter();
filter.addCondition(new Condition.Builder().
setComparison(Comparison.eq).
setField("brand").
setValue("volkswagen").
build()
);
List<Car> carList = repository.findAll(filter);
assertThat(carList.isEmpty(), is(false));
assertEquals(carList.get(0).brand, "volkswagen");
}
Для тестирования и, возможно, для дополнения условий фильтра на серверной части создан билдер. Данный фильтр идеально подходит при работе с REST сервисами. Мы дополнили crud репозитории фильтром который может формироваться из json благодаря чему в добавок к crud методам стала возможна динамическая фильтрация просто создав интерфейс репозитория.
С исходным кодом можно ознакомиться тут: https://github.com/VovaOne/spring-data-specification-accurate-filter