Необязательные параметры в репозиториях Spring Data

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

Чтобы понять проблемы, представим что мы создаем простое приложение- записную книжку, в котором определили сущность- Person с полями id, firstName, lastName, phoneNumber.

Person.java

@Entity @Data @AllArgsConstructor @NoArgsConstructor @EqualsAndHashCode(of = "id") public class Person {     @Id     private Long id;     private String firstName;     private String lastName;     private String phoneNumber; } 

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

  1. Работать напрямую с базой данных через SQL, создать метод в сервисе, который динамически сформирует SQL запрос. Что-то вроде
    Динамический запрос

      Iterable<Person> find(String firstName, String lastName, String phoneNumber) {     List<String> where = new ArrayList();     List params = new ArrayList();     if(firstName != null) {       params.add(firstName);       where.add("first_name = ?");     }     if(lastName != null) {       params.add(lastName);       where.add("last_name = ?");     }     if(phoneNumber != null) {       params.add(phoneNumber);       where.add("phone_number = ?");     }      String sql = "SELECT * FROM person " + (where.isEmpty() ? "" : " WHERE " + String.join(" AND ", where));     // Вызов SQL через JDBCTemplate     // ...   }             

  2. Использовать аннотацию Query для метода поиска с проверкой параметров на null.
    @Query

      @Query("SELECT p FROM Person p " +          "WHERE " +          "(firstName = :firstName or :firstName is null) and " +          "(lastName = :lastName or :lastName is null) and " +          "(phoneNumber = :phoneNumber or :phoneNumber is null)"             )   Iterable<Person> find(               @Param("firstName") String firstName,               @Param("lastName") String lastName,               @Param("phoneNumber") String phoneNumber             );             

  3. Создать методы поиска под все возможные комбинации параметров и вызывать нужный метод после проверки параметров на null.
    Много find методов

      @Repository   public interface PersonRepo extends PagingAndSortingRepository<Person, Long> {     // Методы поиска для разных комбинаций параметров     Iterable<Person> phoneNumberContains(String number);     Iterable<Person> lastName(String lastName);     Iterable<Person> lastNameAndPhoneNumberContains(String lastName, String number);     Iterable<Person> firstName(String firstName);     Iterable<Person> firstNameAndPhoneNumberContains(String firstName, String number);     Iterable<Person> firstNameAndLastName(String firstName, String lastName);     Iterable<Person> firstNameAndLastNameAndPhoneNumberContains(String firstName, String lastName, String number);      // Метод поиска, который определяет какой вариант сработал     default Iterable<Person> findByFirstNameAndLastNameAndPhoneNumberContains(String firstName, String lastName, String number) {       if(firstName == null) {         if(lastName == null) {           if(number == null) {             return findAll();           } else {             return phoneNumberContains(number);           }         } else {           if(number == null) {             return lastName(lastName);           } else {             return lastNameAndPhoneNumberContains(lastName, number);           }         }       } else {         if(lastName == null) {           if(number == null) {             return firstName(firstName);           } else {             return firstNameAndPhoneNumberContains(firstName, number);           }         } else {           if(number == null) {             return firstNameAndLastName(firstName, lastName);           } else {             return firstNameAndLastNameAndPhoneNumberContains(firstName, lastName, number);           }         }       }     }   }             

Я не буду анализировать достоинства и недостатки каждого способа- они очевидны. Скажу лишь, что я выбрал третий вариант с добавлением методов поиска под каждый вариант комбинации параметров и создал OpenSource бибилиотеку, которая использует механизм Annotation Processor и на этапе компиляции и делает всю работу за вас. Чтобы воспользоваться ею- необходимо подключить библиотеку (последнюю версию смотрите на https://github.com/ukman/kolobok или https://mvnrepository.com/artifact/com.github.ukman/kolobok).

<dependency>     <groupId>com.github.ukman</groupId>     <artifactId>kolobok</artifactId>     <version>0.1.2</version>     <scope>compile</scope> </dependency> 

Затем нужно пометить метод, который должен работать по-новому аннотацией @FindWithOptionalParams.

@Repository public interface PersonRepo extends PagingAndSortingRepository<Person, Long> {   @FindWithOptionalParams   Iterable<Person> findByFirstNameAndLastNameAndPhoneNumberContains(String firstName, String lastName, String number); } 

Библиотека сама сгенерирует все методы поиска и default реализацию с проверкой параметров на null и вызовом необходимого метода.

P.S.: напишите в комментариях, какие бы еще аннотации могли бы упростить вам работу со Spring-ом, возможно их тоже добавлю.

ссылка на оригинал статьи https://habr.com/ru/post/483796/

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *