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

后端 未结 3 1173
一向
一向 2020-11-30 22:49

I\'m currently building a REST API in which I want clients to easily filter on most properties of a specific entity. Using QueryDSL in combination with Spring Data REST (an

相关标签:
3条回答
  • 2020-11-30 23:30

    This is what I used for a generic binding for all date fields, always expecting 2 values, from and 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);
    });
    

    This is for datetime fields. For a date field, when getting a single parameter, path.eq() makes sense I guess.

    0 讨论(0)
  • 2020-11-30 23:37

    As it was posted in some comment I also had the need to have different behaviour according to the field name creationDateFrom and creationDateTo. In order to make it work I did the following:

    First I added the @QueryEntity annotation and two more fields to my entity class. The fields were annotated with:

    • @Transient so the fields are not persisted
    • @Getter(value = AccessLevel.PRIVATE) as we are using Lombok, the annotation hides the field from the response body
    • @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) takes care of the format for parsing the date on the url query parameter

    @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;
    
      ...
    }  
    

    Then I changed the way of generating the querydsl classes from JPAAnnotationProcessor to QuerydslAnnotationProcessor. This way fields annotated with @Transient are still generated on QMyEntity but are not persisted. Plugin configuration in 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>
    

    Finally I extended the QuerydslBinderCustomizer and customized the bindings related with the creationDateFrom and creationDateTo but applying the right logic over 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));
    }
    

    With all of this you can do date range queries using one, both or none of the criterias:

    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
    
    0 讨论(0)
  • 2020-11-30 23:47

    I think you should be able to get this to work using the following customization:

    bindings.bind(user.dateOfBirth).all((path, value) -> {
    
      Iterator<? extends LocalDate> it = value.iterator();
      return path.between(it.next(), it.next());
    });
    

    The key here is to use ?dateOfBirth=…&dateOfBirth= (use the property twice) and the ….all(…) binding which will give you access to all values provided.

    Make sure you add the @DateTimeFormat annotation to the dateOfBirth-property of User so that Spring is able to convert the incoming Strings into LocalDate instances correctly.

    The lambda currently gets a Collection<? extends T> which makes untangling the individual elements a bit more pain that it needs to be, but I think we can change this in a future release to rather expose a List.

    0 讨论(0)
提交回复
热议问题