Может ли Spring Интеграция данных REST QueryDSL использоваться для выполнения более сложных запросов?

В настоящее время я создаю REST API, в котором я хочу, чтобы клиенты могли легко фильтровать большинство свойств определенного объекта. Используя QueryDSL в сочетании с Spring Data REST (пример Оливера Гирке) позволяет мне легко получить до 90% того, что я хочу, разрешив клиентам фильтровать, объединив параметры запроса, которые относятся к свойства (например, /users?firstName=Dennis&lastName=Laumen).

Я даже могу настроить сопоставление между параметрами запроса и свойствами сущности, реализовав интерфейс QuerydslBinderCustomizer (например, для запросов, нечувствительных к регистру или неполных строк). Все это здорово, однако я также хочу, чтобы клиенты могли фильтровать некоторые типы с использованием диапазонов. Например, в отношении свойства, подобного дате рождения, я хотел бы сделать что-то вроде следующего: /users?dateOfBirthFrom=1981-1-1&dateOfBirthTo=1981-12-31. То же самое относится к свойствам, основанным на количестве, /users?idFrom=100&idTo=200. У меня такое ощущение, что это возможно с использованием интерфейса QuerydslBinderCustomizer, но интеграция между этими двумя библиотеками не документируется очень широко.

Заключение, возможно ли это с помощью Spring Data REST и QueryDSL? Если да, то как?

Ответы

Ответ 1

Я думаю, вы сможете заставить это работать, используя следующую настройку:

bindings.bind(user.dateOfBirth).all((path, value) -> {

  Iterator<? extends LocalDate> it = value.iterator();
  return path.between(it.next(), it.next());
});

Ключевым моментом здесь является использование ?dateOfBirth=…&dateOfBirth= (использование свойства дважды) и привязка ….all(…), которая даст вам доступ ко всем предоставленным значениям.

Обязательно добавьте аннотацию @DateTimeFormat к dateOfBirth -property User, чтобы Spring смог правильно преобразовать входящие экземпляры Strings в LocalDate.

В настоящее время лямбда получает Collection<? extends T>, что делает распутывание отдельных элементов немного больнее, чем должно быть, но я думаю, что мы можем изменить это в будущей версии, чтобы скорее разоблачить List.

Ответ 2

Это то, что я использовал для общей привязки для всех полей даты, всегда ожидая 2 значения, от и до.

bindings.bind(Date.class).all((final DateTimePath<Date> path, final Collection<? extends Date> values) -> {
    final List<? extends Date> dates = new ArrayList<>(values);
    Collections.sort(dates);
    if (dates.size() == 2) {
        return path.between(dates.get(0), dates.get(1));
    }
    throw new IllegalArgumentException("2 date params(from & to) expected for:" + path + " found:" + values);
});

Это для полей datetime. Для поля даты при получении одного параметра path.eq() имеет смысл, я думаю.

Ответ 3

Как было опубликовано в некоторых комментариях, мне также потребовалось иметь другое поведение в соответствии с именем поля creationDateFrom и creationDateTo. Чтобы сделать это, я сделал следующее:

Сначала я добавил аннотацию @QueryEntity и еще два поля в класс сущности. Поля были аннотированы:

  • @Transient, чтобы поля не сохранялись.
  • @Getter(value = AccessLevel.PRIVATE), поскольку мы используем Lombok, аннотация скрывает поле из тела ответа
  • @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) выполняет поиск формата для разбора date в параметре запроса url

@QueryEntity
@Entity
public class MyEntity implements Serializable {
  ...

  @Column(updatable = false)
  @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
  private Date creationDate;

  @Transient
  @Getter(value = AccessLevel.PRIVATE)
  @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
  private Date creationDateTo;

  @Transient
  @Getter(value = AccessLevel.PRIVATE)
  @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
  private Date creationDateFrom;

  ...
}  

Затем я изменил способ генерации классов querydsl от JPAAnnotationProcessor до QuerydslAnnotationProcessor. Таким образом, поля, аннотированные с помощью @Transient, по-прежнему генерируются на QMyEntity, но не сохраняются. Конфигурация плагина в pom:

<plugin>
    <groupId>com.mysema.maven</groupId>
    <artifactId>apt-maven-plugin</artifactId>
    <version>1.1.3</version>
    <executions>
        <execution>
            <phase>generate-sources</phase>
            <goals>
                <goal>process</goal>
            </goals>
            <configuration>
                <outputDirectory>target/generated-sources/annotations</outputDirectory>
                <processor>com.querydsl.apt.QuerydslAnnotationProcessor</processor>
            </configuration>
        </execution>
    </executions>
</plugin>

Наконец, я расширил QuerydslBinderCustomizer и настроил привязки, связанные с creationDateFrom и creationDateTo, но применяя правильную логику над creationDate

@Override
default void customize(QuerydslBindings bindings, QMyEntity root) {
    bindings.bind(root.creationDateFrom).first((path, value) -> 
                                                root.creationDate.after(value));
    bindings.bind(root.creationDateTo).first((path, value) ->
                                               root.creationDate.before(value));
}

Со всем этим вы можете задавать запросы диапазона дат, используя один или оба критерия:

http://localhost:8080/myentities?creation_date_to=2017-05-08
http://localhost:8080/myentities?creation_date_from=2017-01-01
http://localhost:8080/myentities?creation_date_from=2017-01-01&creation_date_to=2017-05-08