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

Posted

技术标签:

【中文标题】Spring Data REST 的 QueryDSL 集成可以用来执行更复杂的查询吗?【英文标题】:Can Spring Data REST's QueryDSL integration be used to perform more complex queries? 【发布时间】:2016-05-11 09:31:50 【问题描述】:

我目前正在构建一个 REST API,我希望客户端可以在其中轻松过滤特定实体的大多数属性。将QueryDSL 与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 吗?如果有,怎么做?

【问题讨论】:

Oliver Gierke 示例未找到!! @amir110 查看 Oliver Drotbohm 的回答。看来奥利弗改名了。 好的。你说得对,但我的意思是这个页面 (gist.github.com/olivergierke/decf03d4948cd58a51bc) 从 github 中删除。 :) 现在可以在这里找到:gist.github.com/odrotbohm/decf03d4948cd58a51bc 【参考方案1】:

我认为您应该能够使用以下自定义使其工作:

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

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

这里的关键是使用?dateOfBirth=…&amp;dateOfBirth=(使用该属性两次)和….all(…)绑定,这将使您能够访问提供的所有值。

确保将@DateTimeFormat 注解添加到UserdateOfBirth-property,以便Spring 能够正确地将传入的Strings 转换为LocalDate 实例。

lambda 目前得到一个Collection&lt;? extends T&gt;,这使得解开单个元素变得更加痛苦,但我认为我们可以在未来的版本中改变它,而是公开一个List

【讨论】:

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

正如在一些评论中发布的那样,我还需要根据字段名称 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

【讨论】:

【参考方案3】:

这是我用于所有日期字段的通用绑定,总是需要 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() 我猜是有意义的。

【讨论】:

以上是关于Spring Data REST 的 QueryDSL 集成可以用来执行更复杂的查询吗?的主要内容,如果未能解决你的问题,请参考以下文章

排除 Spring-data-rest 资源的部分字段

如何在 Spring-Data-Rest 中实现细粒度的访问控制?

初入spring boot(八 )Spring Data REST

Spring-Data-Rest中时间的数据类型

您如何保护 Spring Boot / Spring-Data Rest 以便用户只能访问他自己的实体

spring-data-rest 集成测试因简单的 json 请求而失败