Spring Data REST 的 QueryDSL 集成是否可以用于执行更复杂的查询?

Can Spring Data REST's QueryDSL integration be used to perform more complex queries?

我目前正在构建一个 REST API,我希望客户可以在其中轻松过滤特定实体的大多数属性。使用 QueryDSL in combination with Spring Data REST (an example by Oliver Gierke) 允许客户通过组合引用属性的查询参数(例如 /users?firstName=Dennis&lastName=Laumen)进行过滤,从而轻松获得我想要的 90%。

我什至可以通过实现 QuerydslBinderCustomizer 接口自定义查询参数和实体属性之间的映射(例如,不区分大小写的搜索或部分字符串匹配)。这一切都很好,但是我也希望客户端能够使用范围过滤某些类型。例如,关于 属性 之类的出生日期,我想做类似下面的事情,/users?dateOfBirthFrom=1981-1-1&dateOfBirthTo=1981-12-31。基于数字的属性也是如此,/users?idFrom=100&idTo=200。我觉得这应该可以使用 QuerydslBinderCustomizer 接口实现,但是这两个库之间的集成没有被广泛记录。

最后,这是否可能使用 Spring Data REST 和 QueryDSL?如果可以,怎么办?

我认为您应该能够使用以下定制来实现它:

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

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

这里的关键是使用 ?dateOfBirth=…&dateOfBirth=(使用 属性 两次)和 ….all(…) 绑定,这样您就可以访问所有提供的值。

确保将 @DateTimeFormat 注释添加到 UserdateOfBirth-属性 以便 Spring 能够转换传入的 Strings 正确转换为 LocalDate 个实例。

lambda 目前有一个 Collection<? extends T>,这使得理清各个元素变得更加痛苦,但我认为我们可以在未来的版本中更改它,而不是暴露一个 List.

这就是我用于所有日期字段的通用绑定,始终需要 2 个值,from 和 to。

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);
});

这适用于日期时间字段。对于日期字段,当获取单个参数时,path.eq() 我猜是有道理的。

由于它在一些评论中发布,我还需要根据字段名称 creationDateFromcreationDateTo 具有不同的行为。为了让它工作,我做了以下事情:

首先,我向我的实体 class 添加了 @QueryEntity 注释和另外两个字段。这些字段注释为:

  • @Transient 所以字段不会持久化
  • @Getter(value = AccessLevel.PRIVATE) 由于我们使用的是 Lombok,注解隐藏了 来自响应正文的字段
  • @DateTimeFormat(iso = DateTimeFormat.ISO.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 classes 的方式从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 并自定义了与 creationDateFromcreationDateTo 相关的绑定,但在 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));
}

有了所有这些,您可以使用一个、两个或 none 条件进行日期范围查询:

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