15

Attualmente sto costruendo un'API REST in cui desidero che i client filtrino facilmente sulla maggior parte delle proprietà di un'entità specifica. L'utilizzo di QueryDSL in combinazione con Spring Data REST (an example by Oliver Gierke) mi consente di ottenere facilmente il 90% di ciò che voglio consentendo ai client di filtrare combinando i parametri di query che fanno riferimento alle proprietà (ad esempio /users?firstName=Dennis&lastName=Laumen).È possibile utilizzare l'integrazione QueryDSL di Spring Data REST per eseguire query più complesse?

Posso anche personalizzare la mappatura tra i parametri di query e le proprietà di un'entità implementando l'interfaccia QuerydslBinderCustomizer (ad esempio per ricerche non sensibili alle maiuscole o parziali di stringhe parziali). Tutto questo è fantastico, ma voglio anche che i client siano in grado di filtrare alcuni tipi utilizzando gli intervalli. Ad esempio per quanto riguarda una proprietà come data di nascita vorrei fare qualcosa come il seguente, /users?dateOfBirthFrom=1981-1-1&dateOfBirthTo=1981-12-31. Lo stesso vale per le proprietà basate sui numeri, /users?idFrom=100&idTo=200. Ho la sensazione che questo dovrebbe essere possibile usando l'interfaccia QuerydslBinderCustomizer ma l'integrazione tra queste due librerie non è documentata molto estesamente.

Concludendo, è possibile utilizzare Spring Data REST e QueryDSL? Se é cosi, come?

risposta

19

penso che si dovrebbe essere in grado di farlo funzionare con la seguente personalizzazione:

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

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

La chiave qui è quello di utilizzare ?dateOfBirth=…&dateOfBirth= (utilizzare la proprietà due volte) e il legame ….all(…) che vi darà accesso a tutti i valori forniti.

Assicurati di aggiungere il @DateTimeFormat annotazioni al dateOfBirth -property di User in modo che la primavera è in grado di convertire correttamente i entranti Strings in LocalDate istanze.

Attualmente il lambda riceve uno Collection<? extends T> che rende meno complessi i singoli elementi di cui ha bisogno, ma credo che in una versione futura potremmo modificare questo valore piuttosto che esporre uno List.

+0

Grazie @ oliver-gierke! L'ho fatto funzionare in base all'esempio che hai fornito e ho aggiunto un po 'di logica condizionale, quindi se viene data una sola data viene usata come una data "da". Puoi aggiungere qualche altra spiegazione aggiuntiva se sia * anche * possibile aggiungere percorsi inesistenti ai binding? Il mio esempio di aggiunta di un parametro di query "dateOfBirthFrom" è possibile anche utilizzando Spring Data REST e QueryDSL? (Solo curioso, hai già risolto il mio problema! Grazie ancora!) –

+1

@DennisLaumen parametro "dateOfBirthFrom" potrebbe essere utile. La personalizzazione del binding di query presuppone che se esiste una sola data utilizzata come da data, giusto? Ma il filtraggio usando solo un appuntamento non sarebbe possibile. Qualche suggerimento @ oliver-gierke? – gazal

+0

@gazal, ho risolto questo problema utilizzando il codice seguente, spero che sia d'aiuto. 'bindings.bind (Date.class) .Tutte ((DateTimePath percorso, Collection Valore) -> { Iterator it = value.iterator();? Data firstTimestamp = it.next(); se (it.hasNext()) { Data secondTimestamp = it.next(); ritorno path.between (firstTimestamp, secondTimestamp);} else { ritorno path.after (firstTimestamp) ; } }); ' –

3

Questo è quello che ho usato per un binding generico per tutti i campi data, sempre aspettando 2 valori, da e verso.

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

Questo è per i campi datetime. Per un campo di data, quando si ottiene un singolo parametro, è opportuno che lo path.eq() abbia un senso.

5

Come è stato pubblicato in alcuni commenti ho anche avuto la necessità di avere un comportamento diverso in base al nome del campo creationDateFrom e creationDateTo. Per farlo funzionare ho fatto quanto segue:

Prima ho aggiunto l'annotazione @QueryEntity e altri due campi alla mia classe di entità.I campi sono stati annotati con:

  • @Transient in modo che i campi non vengono mantenute
  • @Getter(value = AccessLevel.PRIVATE) come stiamo usando Lombok, l'annotazione nasconde il campo dal corpo della risposta
  • @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) si occupa del formato per l'analisi la data sul parametro di query 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; 

    ... 
} 

Quindi ho cambiato il modo di generare le classi querydsl da JPAAnnotationProcessor a QuerydslAnnotationProcessor. In questo modo i campi annotati con @Transient vengono comunque generati su QMyEntity ma non vengono mantenuti. configurazione del plugin 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> 

Infine ho esteso il QuerydslBinderCustomizer e personalizzata le associazioni connesse con la creationDateFrom e creationDateTo ma applicando la logica destra sopra 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)); 
} 

Con tutto questo si può fare intervallo di date query utilizzando uno, entrambi o nessuno dei criteri:

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