1. Введение
В этом руководстве мы узнаем, как запрашивать данные с помощью Spring Data Query by Example API .
Во-первых, мы определим схему данных, которые мы хотим запросить. Далее мы рассмотрим несколько соответствующих классов из Spring Data. А затем мы рассмотрим несколько примеров.
Давайте начнем!
2. Тестовые данные
Наши тестовые данные представляют собой список имен пассажиров, а также места, которые они занимали.
| Имя | Фамилия | Номер места |
| Джилл | Смит | 50 |
| Канун | Джексон | 94 |
| Фред | Блоги | 22 |
| Рики | Бобби | 36 |
| Сия | Колиси | 85 |
3. Домен
Давайте создадим нужный нам репозиторий данных Spring и предоставим наш класс домена и тип идентификатора.
Для начала мы смоделировали нашего пассажира
как сущность JPA:
@Entity
class Passenger {
@Id
@GeneratedValue
@Column(nullable = false)
private Long id;
@Basic(optional = false)
@Column(nullable = false)
private String firstName;
@Basic(optional = false)
@Column(nullable = false)
private String lastName;
@Basic(optional = false)
@Column(nullable = false)
private int seatNumber;
// constructor, getters etc.
}
Вместо использования JPA мы могли бы смоделировать его как еще одну абстракцию.
4. Запрос по примеру API
Во-первых, давайте взглянем на интерфейс JpaRepository
. Как мы видим, он расширяет интерфейс QueryByExampleExecutor
для поддержки запроса на примере:
public interface JpaRepository<T, ID>
extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {}
Этот интерфейс представляет больше вариантов метода find()
, с которым мы знакомы из Spring Data. Однако каждый метод также принимает экземпляр Example
:
public interface QueryByExampleExecutor<T> {
<S extends T> Optional<S> findOne(Example<S> var1);
<S extends T> Iterable<S> findAll(Example<S> var1);
<S extends T> Iterable<S> findAll(Example<S> var1, Sort var2);
<S extends T> Page<S> findAll(Example<S> var1, Pageable var2);
<S extends T> long count(Example<S> var1);
<S extends T> boolean exists(Example<S> var1);
}
Во-вторых, интерфейс Example
предоставляет методы для доступа к зонду
и ExampleMatcher
.
Важно понимать, что probe
— это экземпляр нашего Entity
:
public interface Example<T> {
static <T> org.springframework.data.domain.Example<T> of(T probe) {
return new TypedExample(probe, ExampleMatcher.matching());
}
static <T> org.springframework.data.domain.Example<T> of(T probe, ExampleMatcher matcher) {
return new TypedExample(probe, matcher);
}
T getProbe();
ExampleMatcher getMatcher();
default Class<T> getProbeType() {
return ProxyUtils.getUserClass(this.getProbe().getClass());
}
}
Таким образом, наш зонд
и наш ExampleMatcher
вместе определяют наш запрос.
5. Ограничения
Как и все, API Query by Example имеет некоторые ограничения. Например:
- Операторы вложенности и группировки не поддерживаются, например:
(
firstName= ?0 и
lastName= ?1) или
seatNumber
= ?2
- Сопоставление строк включает только точное, без учета регистра, начало, конец, содержит и регулярное выражение.
- Все типы, кроме
String
, являются только точными совпадениями.
Теперь, когда мы немного познакомились с API и его ограничениями, давайте рассмотрим несколько примеров.
6. Примеры
6.1. Сопоставление с учетом регистра
Давайте начнем с простого примера и поговорим о поведении по умолчанию:
@Test
public void givenPassengers_whenFindByExample_thenExpectedReturned() {
Example<Passenger> example = Example.of(Passenger.from("Fred", "Bloggs", null));
Optional<Passenger> actual = repository.findOne(example);
assertTrue(actual.isPresent());
assertEquals(Passenger.from("Fred", "Bloggs", 22), actual.get());
}
В частности, статический метод Example.of() создает пример
,
используя ExampleMatcher.matching()
.
Другими словами, точное соответствие будет выполнено для всех ненулевых свойств Passenger
. Таким образом, сопоставление чувствительно к регистру в свойствах String .
Однако было бы не слишком полезно, если бы все, что мы могли сделать, — это точное совпадение всех ненулевых свойств.
Здесь на помощь приходит ExampleMatcher
. Создав собственный ExampleMatcher
, мы можем настроить его поведение в соответствии с нашими потребностями.
6.2. Сопоставление без учета регистра
Имея это в виду, давайте взглянем на другой пример, на этот раз используя withIgnoreCase()
для достижения соответствия без учета регистра:
@Test
public void givenPassengers_whenFindByExampleCaseInsensitiveMatcher_thenExpectedReturned() {
ExampleMatcher caseInsensitiveExampleMatcher = ExampleMatcher.matchingAll().withIgnoreCase();
Example<Passenger> example = Example.of(Passenger.from("fred", "bloggs", null),
caseInsensitiveExampleMatcher);
Optional<Passenger> actual = repository.findOne(example);
assertTrue(actual.isPresent());
assertEquals(Passenger.from("Fred", "Bloggs", 22), actual.get());
}
Обратите внимание, что в этом примере мы сначала вызвали ExampleMatcher.matchingAll() —
он ведет себя так же, как и ExampleMatcher.matching()
, который мы использовали в предыдущем примере.
6.3. Пользовательское соответствие
Мы также можем настроить поведение нашего сопоставления для каждого свойства и сопоставить любое свойство с помощью ExampleMatcher.matchingAny()
:
@Test
public void givenPassengers_whenFindByExampleCustomMatcher_thenExpectedReturned() {
Passenger jill = Passenger.from("Jill", "Smith", 50);
Passenger eve = Passenger.from("Eve", "Jackson", 95);
Passenger fred = Passenger.from("Fred", "Bloggs", 22);
Passenger siya = Passenger.from("Siya", "Kolisi", 85);
Passenger ricki = Passenger.from("Ricki", "Bobbie", 36);
ExampleMatcher customExampleMatcher = ExampleMatcher.matchingAny()
.withMatcher("firstName", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
.withMatcher("lastName", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase());
Example<Passenger> example = Example.of(Passenger.from("e", "s", null), customExampleMatcher);
List<Passenger> passengers = repository.findAll(example);
assertThat(passengers, contains(jill, eve, fred, siya));
assertThat(passengers, not(contains(ricki)));
}
6.4. Игнорирование свойств
С другой стороны, мы также можем запрашивать только часть наших свойств .
Мы достигаем этого, игнорируя некоторые свойства, используя ExampleMatcher.ignorePaths(String… paths)
:
@Test
public void givenPassengers_whenFindByIgnoringMatcher_thenExpectedReturned() {
Passenger jill = Passenger.from("Jill", "Smith", 50);
Passenger eve = Passenger.from("Eve", "Jackson", 95);
Passenger fred = Passenger.from("Fred", "Bloggs", 22);
Passenger siya = Passenger.from("Siya", "Kolisi", 85);
Passenger ricki = Passenger.from("Ricki", "Bobbie", 36);
ExampleMatcher ignoringExampleMatcher = ExampleMatcher.matchingAny()
.withMatcher("lastName", ExampleMatcher.GenericPropertyMatchers.startsWith().ignoreCase())
.withIgnorePaths("firstName", "seatNumber");
Example<Passenger> example = Example.of(Passenger.from(null, "b", null), ignoringExampleMatcher);
List<Passenger> passengers = repository.findAll(example);
assertThat(passengers, contains(fred, ricki));
assertThat(passengers, not(contains(jill));
assertThat(passengers, not(contains(eve));
assertThat(passengers, not(contains(siya));
}
7. Заключение
В этой статье мы продемонстрировали, как использовать Query by Example API.
Мы продемонстрировали, как использовать Example
и ExampleMatcher
вместе с интерфейсом QueryByExampleExecutor
для запроса таблицы с использованием примера экземпляра данных.
В заключение вы можете найти код на GitHub .