Чтобы понять проблемы, представим что мы создаем простое приложение- записную книжку, в котором определили сущность- Person с полями id, firstName, lastName, phoneNumber.
@Entity @Data @AllArgsConstructor @NoArgsConstructor @EqualsAndHashCode(of = "id") public class Person { @Id private Long id; private String firstName; private String lastName; private String phoneNumber; }
Допустим мы должны обеспечить поиск по имени, фамилии и части телефонного номера или по любой комбинации этих параметров и хотим, чтобы метод поиска не учитывал параметры, значения которых равны null. Для решения этой задачи можно пойти несколькими путями:
- Работать напрямую с базой данных через 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 // ... }
- Использовать аннотацию 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 );
- Создать методы поиска под все возможные комбинации параметров и вызывать нужный метод после проверки параметров на 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/