15

Ich erstelle derzeit eine REST-API, in der Clients auf einfache Weise die meisten Eigenschaften einer bestimmten Entität filtern sollen. Die Verwendung von QueryDSL in Kombination mit Spring Data REST (an example by Oliver Gierke) ermöglicht es mir, leicht zu 90% von dem zu kommen, was ich möchte, indem es Clients erlaubt, durch Kombinieren von Abfrageparametern zu filtern, die sich auf Eigenschaften beziehen (z.B. /users?firstName=Dennis&lastName=Laumen).Can Spring Data Die QueryDSL-Integration von REST wird für komplexere Abfragen verwendet.

Ich kann sogar die Zuordnung zwischen den Abfrageparametern und den Eigenschaften einer Entität anpassen, indem ich die Schnittstelle QuerydslBinderCustomizer implementiere (z. B. für die Suche nach Groß-/Kleinschreibung oder Teilzeichenfolge). Das ist alles großartig, aber ich möchte auch, dass die Clients in der Lage sind, einige Typen mit Bereichen zu filtern. Zum Beispiel in Bezug auf eine Eigenschaft wie Geburtsdatum möchte ich etwas wie das folgende tun, /users?dateOfBirthFrom=1981-1-1&dateOfBirthTo=1981-12-31. Dasselbe gilt für auf Zahlen basierende Eigenschaften, /users?idFrom=100&idTo=200. Ich habe das Gefühl, dass dies mit der QuerydslBinderCustomizer Schnittstelle möglich sein sollte, aber die Integration zwischen diesen beiden Bibliotheken ist nicht sehr ausführlich dokumentiert.

Abschließend ist dies mit Spring Data REST und QueryDSL möglich? Wenn das so ist, wie?

Antwort

19

Ich denke, Sie sollten dies mit dem folgenden Anpassung an die Arbeit machen können:

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

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

Der Schlüssel hier ist ?dateOfBirth=…&dateOfBirth= zu verwenden (verwenden Sie die Eigenschaft zweimal) und die ….all(…) Bindung, die Sie geben Zugriff auf alle zur Verfügung gestellten Werte.

Achten Sie darauf, die @DateTimeFormat Anmerkung zum dateOfBirth -property von User hinzufügen, so dass Frühling ist in der Lage, die eingehenden Strings in LocalDate Instanzen korrekt zu konvertieren.

Das Lambda bekommt derzeit eine Collection<? extends T>, die das Entwirren der einzelnen Elemente ein wenig mehr Schmerzen macht, die es sein muss, aber ich denke, wir können dies in einer zukünftigen Version ändern, um eher eine List zu exponieren.

+0

Danke @ oliver-gierke! Ich habe es basierend auf dem Beispiel, das Sie gegeben haben, zum Laufen gebracht und etwas mehr bedingte Logik hinzugefügt. Wenn nur ein Datum angegeben wird, wird es als "Von" -Datum verwendet. Könnten Sie noch eine zusätzliche Erklärung geben, ob es * auch * möglich ist, den Bindings nicht existierende Pfade hinzuzufügen? Ist mein Beispiel zum Hinzufügen eines "dateOfBirthFrom" -Abfrageparameters mit Spring Data REST und QueryDSL sogar möglich? (Nur neugierig, Sie lösten mein Problem bereits! Nochmals vielen Dank!) –

+1

@DennisLaumen "dateOfBirthFrom" Stil Query-Parameter könnte hilfreich sein. Bei der Anpassung der Abfragebindung wird davon ausgegangen, dass, wenn nur ein Datum vorhanden ist, dieses Datum verwendet wird, oder? Eine Filterung mit nur einem Datum wäre jedoch nicht möglich. Irgendwelche Vorschläge @ oliver-gierke? – gazal

+0

@gazal, habe ich das mit dem folgenden Code behoben, hoffe es hilft. 'bindings.bind (Date.class) .Alle ((DateTimePath Weg, Collection Wert) -> { Iterator it = value.iterator(); Datum firstTimestamp = it.next(); if (it.hasNext()) { Datum secondTimestamp = it.next(); return path.between (firstTimestamp, secondTimestamp); } else { return path.after (firstTimestamp) ; } }); ' –

3

Dies ist, was ich für eine generische Bindung für alle Datumsfelder verwendet habe, immer 2 Werte von und zu erwarten.

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

Dies ist für Datetime-Felder. Für ein Datumsfeld, wenn ich einen einzigen Parameter erhalte, ist path.eq() sinnvoll, denke ich.

5

Wie es in einem Kommentar geschrieben wurde, hatte ich auch die Notwendigkeit, unterschiedliche Verhalten entsprechend dem Feldnamen und creationDateTo zu haben. Um es zum Laufen zu bringen, habe ich folgendes gemacht:

Zuerst habe ich die @QueryEntity Annotation und zwei weitere Felder zu meiner Entitätsklasse hinzugefügt.Die Felder wurden mit kommentierten:

  • @Transient, so dass die Felder nicht
  • beharrte
  • @Getter(value = AccessLevel.PRIVATE) wie wir Lombok verwenden, versteckt sich die Anmerkung des Feld aus der Antworttext
  • @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Pflege des Formats für die Analyse dauert das Datum auf dem uRL-Abfrageparameter

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

    ... 
} 

Dann änderte ich die Art der Generierung der Querydsl-Klassen von JPAAnnotationProcessor zu QuerydslAnnotationProcessor. Auf diese Weise werden Felder, die mit @Transient annotiert sind, weiterhin unter QMyEntity generiert, aber nicht beibehalten. Plugin-Konfiguration 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> 

Schließlich verlängert ich die QuerydslBinderCustomizer und individuell auf die Bindungen im Zusammenhang mit dem creationDateFrom und creationDateTo aber die Anwendung die richtige Logik über 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)); 
} 

Mit all dies Sie Datumsbereich tun können Abfragen mit einem, keinem oder keinem der folgenden Kriterien:

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 
Verwandte Themen