Автор оригинала: Eugen Paraschiv.
1. Обзор
В этой первой статье этой новой серии мы рассмотрим простой язык запросов для REST API . Мы будем хорошо использовать Spring для API REST и критериев JPA 2 для аспектов персистентности.
Почему именно язык запросов? Потому что – для любого достаточно сложного API – поиска/фильтрации ваших ресурсов по очень простым полям просто недостаточно. Язык запросов является более гибким и позволяет отфильтровать именно те ресурсы, которые вам нужны.
2. Сущность пользователя
Во – первых, давайте выдвинем простую сущность, которую мы собираемся использовать для нашего API фильтра/поиска – базовый Пользователь :
@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String firstName; private String lastName; private String email; private int age; }
3. Фильтр С Помощью Построителя Критериев
Теперь – давайте перейдем к сути проблемы – запросу на уровне персистентности.
Построение абстракции запроса – это вопрос баланса. С одной стороны, нам нужна большая гибкость, а с другой-мы должны поддерживать управляемость сложностью. Высокий уровень, функциональность проста – вы передаете некоторые ограничения и получаете некоторые результаты .
Давайте посмотрим, как это работает:
@Repository public class UserDAO implements IUserDAO { @PersistenceContext private EntityManager entityManager; @Override public ListsearchUser(List params) { CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery query = builder.createQuery(User.class); Root r = query.from(User.class); Predicate predicate = builder.conjunction(); UserSearchQueryCriteriaConsumer searchConsumer = new UserSearchQueryCriteriaConsumer(predicate, builder, r); params.stream().forEach(searchConsumer); predicate = searchConsumer.getPredicate(); query.where(predicate); List result = entityManager.createQuery(query).getResultList(); return result; } @Override public void save(User entity) { entityManager.persist(entity); } }
Давайте посмотрим на Критерии пользовательского поискового запроса/| класс:
public class UserSearchQueryCriteriaConsumer implements Consumer{ private Predicate predicate; private CriteriaBuilder builder; private Root r; @Override public void accept(SearchCriteria param) { if (param.getOperation().equalsIgnoreCase(">")) { predicate = builder.and(predicate, builder .greaterThanOrEqualTo(r.get(param.getKey()), param.getValue().toString())); } else if (param.getOperation().equalsIgnoreCase("<")) { predicate = builder.and(predicate, builder.lessThanOrEqualTo( r.get(param.getKey()), param.getValue().toString())); } else if (param.getOperation().equalsIgnoreCase(":")) { if (r.get(param.getKey()).getJavaType() == String.class) { predicate = builder.and(predicate, builder.like( r.get(param.getKey()), "%" + param.getValue() + "%")); } else { predicate = builder.and(predicate, builder.equal( r.get(param.getKey()), param.getValue())); } } } // standard constructor, getter, setter }
Как вы можете видеть, пользователь search API берет список очень простых ограничений, составляет запрос на основе этих ограничений, выполняет поиск и возвращает результаты.
Класс ограничений также довольно прост:
public class SearchCriteria { private String key; private String operation; private Object value; }
Критерии поиска реализация содержит наши Параметры запроса :
- ключ : используется для хранения имени поля – например: Имя , возраст , … и т.д.
- операция : используется для удержания операции – например: Равенство, меньше, … и т. Д.
- значение : используется для хранения значения поля – например: джон, 25, … и т. Д.
4. Проверьте поисковые запросы
Теперь давайте проверим наш механизм поиска, чтобы убедиться, что он выдерживает воду.
Во – первых, давайте инициализируем нашу базу данных для тестирования, добавив двух пользователей, как в следующем примере:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { PersistenceConfig.class }) @Transactional @TransactionConfiguration public class JPACriteriaQueryTest { @Autowired private IUserDAO userApi; private User userJohn; private User userTom; @Before public void init() { userJohn = new User(); userJohn.setFirstName("John"); userJohn.setLastName("Doe"); userJohn.setEmail("[email protected]"); userJohn.setAge(22); userApi.save(userJohn); userTom = new User(); userTom.setFirstName("Tom"); userTom.setLastName("Doe"); userTom.setEmail("[email protected]"); userTom.setAge(26); userApi.save(userTom); } }
Теперь давайте получим Пользователя с конкретным именем и Фамилией – как в следующем примере:
@Test public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() { Listparams = new ArrayList (); params.add(new SearchCriteria("firstName", ":", "John")); params.add(new SearchCriteria("lastName", ":", "Doe")); List results = userApi.searchUser(params); assertThat(userJohn, isIn(results)); assertThat(userTom, not(isIn(results))); }
Далее, давайте получим Список | Пользователя с той же фамилией :
@Test public void givenLast_whenGettingListOfUsers_thenCorrect() { Listparams = new ArrayList (); params.add(new SearchCriteria("lastName", ":", "Doe")); List results = userApi.searchUser(params); assertThat(userJohn, isIn(results)); assertThat(userTom, isIn(results)); }
Далее, давайте получим пользователей с возрастом | больше или равным 25 :
@Test public void givenLastAndAge_whenGettingListOfUsers_thenCorrect() { Listparams = new ArrayList (); params.add(new SearchCriteria("lastName", ":", "Doe")); params.add(new SearchCriteria("age", ">", "25")); List results = userApi.searchUser(params); assertThat(userTom, isIn(results)); assertThat(userJohn, not(isIn(results))); }
Далее, давайте искать пользователей, которые на самом деле не существуют :
@Test public void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() { Listparams = new ArrayList (); params.add(new SearchCriteria("firstName", ":", "Adam")); params.add(new SearchCriteria("lastName", ":", "Fox")); List results = userApi.searchUser(params); assertThat(userJohn, not(isIn(results))); assertThat(userTom, not(isIn(results))); }
Наконец, давайте искать пользователей, которым дано только частичное |/имя :
@Test public void givenPartialFirst_whenGettingListOfUsers_thenCorrect() { Listparams = new ArrayList (); params.add(new SearchCriteria("firstName", ":", "jo")); List results = userApi.searchUser(params); assertThat(userJohn, isIn(results)); assertThat(userTom, not(isIn(results))); }
6. Пользовательский контроллер
Наконец, давайте теперь подключим поддержку персистентности для этого гибкого поиска к нашему API REST.
Мы собираемся настроить простой UserController – с помощью findAll () | с помощью ” search ” для передачи всего выражения поиска/фильтра :
@Controller public class UserController { @Autowired private IUserDao api; @RequestMapping(method = RequestMethod.GET, value = "/users") @ResponseBody public ListfindAll(@RequestParam(value = "search", required = false) String search) { List params = new ArrayList (); if (search != null) { Pattern pattern = Pattern.compile("(\w+?)(:|<|>)(\w+?),"); Matcher matcher = pattern.matcher(search + ","); while (matcher.find()) { params.add(new SearchCriteria(matcher.group(1), matcher.group(2), matcher.group(3))); } } return api.searchUser(params); } }
Обратите внимание, как мы просто создаем объекты критериев поиска из выражения поиска.
Сейчас мы находимся на том этапе, когда мы можем начать играть с API и убедиться, что все работает правильно:
http://localhost:8080/users?search=lastName:doe,age>25
И вот его ответ:
[{ "id":2, "firstName":"tom", "lastName":"doe", "email":"[email protected]", "age":26 }]
7. Заключение
Эта простая, но мощная реализация позволяет довольно много интеллектуальной фильтрации в REST API. Да, это все еще грубо по краям и может быть улучшено (и будет улучшено в следующей статье), но это надежная отправная точка для реализации такого рода функций фильтрации в ваших API.
полную реализацию этой статьи можно найти в проекте GitHub .