Чтобы понять проблемы, представим что мы создаем простое приложение- записную книжку, в котором определили сущность- 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-ом, возможно их тоже добавлю.
maxzh83
А почему не рассматриваете Specifications (или Querydsl)? Для фильтрации по динамическим параметрам вполне себе вариант. По крайней мере «магии» там поменьше, чем в вашем варианте с аннотацией.
ukman Автор
Честно говоря ни разу не использовал Specification- как правило если мне не хватает в Spring Data обычных find методов, то мне проще всегда сразу взяться за SQL. Именно поэтому, чтобы реже расчехлять SQL-пушку, хотелось побольше "магии" для find методов.
ruslanys
А вы посмотрите. Specification или QueryDSL – как раз то, что Вам нужно.