Перейти к основному содержимому

Запрос Spring Data JPA на примере

· 5 мин. чтения

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 .