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

33

我正在构建一个REST API,希望客户端可以轻松地过滤特定实体的大多数属性。使用QueryDSLSpring Data RESTOliver 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是否可能实现此操作?如果可以,如何实现?


Oliver Gierke 的示例未找到!! - Amir Azizkhani
@amir110 请查看Oliver Drotbohm的回答。看起来Oliver已经更改了他的名字。 - Dennis Laumen
好的,你是对的,但我的意思是这个页面(https://gist.github.com/olivergierke/decf03d4948cd58a51bc)已经从GitHub上删除了。 :) - Amir Azizkhani
1
现在可以在这里找到:https://gist.github.com/odrotbohm/decf03d4948cd58a51bc - wjans
3个回答

30

我认为你应该能够使用以下自定义来使其正常工作:

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

感谢@oliver-gierke!我根据您给出的示例使其工作,并添加了一些更多的条件逻辑,因此如果只提供一个日期,则将其用作“从”日期。您能否提供更多的附加说明,是否可以将不存在的路径添加到绑定中?使用Spring Data REST和QueryDSL是否可能添加“dateOfBirthFrom”查询参数的示例?(只是好奇,您已经解决了我的问题!再次感谢!) - Dennis Laumen
1
@DennisLaumen,“dateOfBirthFrom”风格的查询参数可能会有所帮助。您的查询绑定自定义假设如果只有一个日期,则将其用作起始日期,对吗?但仅使用截止日期进行过滤是不可能的。有任何建议吗,@oliver-gierke? - gazal
1
@DennisLaumen 谢谢你的回复。我的意思是让客户有灵活性,可以指定一个日期字段的单个参数应该作为前过滤器还是后过滤器。经过进一步的检查,我认为在这种情况下,可以安全地期望客户端发送像“-99999-01-01”这样的日期作为下限界限或者“99999-01-01”作为正无穷界限。 - gazal
1
@gazal 感谢您提供的额外信息!我相信这将有助于未来访问此页面的用户。 - Dennis Laumen
8
@Oliver-gierke - 有没有计划让QuerydslPredicateArgumentResolver最终从URL本身构建谓词?通常在事先设置谓词行为方面限制太多,如果QuerydslPredicateArgumentResolver可以从查询字符串中构造谓词,就会很好,例如:<endpoint>/products/?price.greaterThan=100&barcode.contains=ABC&saleEndDate.between=2017-08-10,2017-08-15。 - mancini0
显示剩余2条评论

15

正如在某个评论中发布的那样,我也需要根据字段名creationDateFromcreationDateTo有不同的行为。为了使其工作,我执行了以下操作:

首先,我添加了@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类的方式,从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));
}

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

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

5
我使用了一个通用绑定来处理所有日期字段,始终期望有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);
});

这是针对日期时间字段的。对于日期字段,在获取单个参数时,path.eq()应该很容易理解。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接