2013-05-28 19 views
12

Come si esegue l'ordine con query di oggetti SQL in JDBI?Ordine dinamico in query oggetti SQL JDBI

voglio fare qualcosa di simile:

@SqlQuery(
    "SELECT * FROM users " + 
    "WHERE something = :something " + 
    "ORDER BY :orderBy :orderDir" 
) 
List<User> getUsers(
    @Bind("something") Integer something 
    , @BindOrderBy("orderBy") String orderBy 
    , @BindOrderDir("orderDir") String orderDir 
); 

o

@SqlQuery(
    "SELECT * FROM users " + 
    "WHERE something = :something " + 
    "ORDER BY :orderBy :orderDir" 
) 
List<User> getUsers(
    @Bind("something") Integer something 
    , @Bind("orderBy") OrderBy orderBy 
    , @Bind("orderDir") OrderDir orderDir 
); 

risposta

26

Recentemente ho esplorato DropWizard in dotazione al JDBI e rapidamente si è imbattuto lo stesso problema. Sfortunatamente JDBI ha una documentazione insufficiente (JavaDoc e alcuni test di unità di esempio sul suo repository git non lo fanno da soli) che è deludente.

Ecco cosa ho trovato che raggiunge un ordine dinamico in un oggetto API SQL per JDBI in base alla mia esempio DAO:

@UseStringTemplate3StatementLocator 
public interface ProductsDao { 

    @RegisterMapperFactory(BeanMapperFactory.class) // will map the result of the query to a list of Product POJOs(Beans) 
    @SqlQuery("select * from products order by <orderby> <order> limit :limit offset :offset") 
    List<Product> getProducts(@Define("orderby") String orderBy, @Define("order") String order, 
            @Bind("limit") int limit, @Bind("offset") int offset); 

    @SqlQuery("select count(*) from products") 
    int getProductsCount(); 

} 

@ UseStringTemplate3StatementLocator - questa annotazione è ciò che ci permette di utilizzare la sintassi <arg> in le domande. Questi argomenti verranno sostituiti con qualsiasi valore fornito tramite l'annotazione @Define.

Per poter utilizzare questa funzione ho dovuto aggiungere inoltre questa dipendenza al mio pom.xml lima:

<dependency> 
    <groupId>antlr</groupId> 
    <artifactId>stringtemplate</artifactId> 
    <version>2.3b6</version> <!-- I am not sure if this specific version is meant to be used though --> 
</dependency> 

SQL Injection ATTENZIONE Va notato che questo ci apre alla Sql Injection in quanto i valori sono direttamente inseriti nella query. (In contstrast alla sintassi :arg nella query e all'annotazione @Bind che utilizza istruzioni preparate e protegge contro l'iniezione sql). Per lo meno è necessario disinfettare i parametri che verranno utilizzati per i campi @Define. (Semplice esempio per DropWizard di seguito).

@Path("/products") 
@Produces(MediaType.APPLICATION_JSON) 
public class ProductsResource { 
    private static ImmutableSet<String> orderByChoices = ImmutableSet.of("id", "name", "price", "manufactureDate"); 

    private final ProductsDao dao; 

    public ProductsResource(ProductsDao dao) { 
    this.dao = dao; 
    } 

    @GET 
    // Use @InjectParam to bind many query parameters to a POJO(Bean) instead. 
    // https://jersey.java.net/apidocs/1.17/jersey/com/sun/jersey/api/core/InjectParam.html 
    // i.e. public List<Product> index(@InjectParam ProductsRequest request) 
    // Also use custom Java types for consuming request parameters. This allows to move such validation/sanitization logic outside the 'index' method. 
    // https://jersey.java.net/documentation/1.17/jax-rs.html#d4e260 
    public List<Product> index(@DefaultValue("id") @QueryParam("orderby") String orderBy, 
          @DefaultValue("asc") @QueryParam("order") String order, 
          @DefaultValue("20") @QueryParam("perpage") IntParam perpage, 
          @DefaultValue("0") @QueryParam("page") IntParam page) 

    int limit, offset; 

    order = order.toLowerCase(); 
    orderBy = orderBy.toLowerCase();  

    if (!orderByChoices.contains(orderBy)) orderBy = "id"; //sanitize <orderby> 
    if (order != "asc" && order != "desc") order = "asc"; //sanitize <order> 

    limit = perpage.get(); 
    offset = page.get() < 0 ? 0 : page.get() * limit; 

    return dao.getProducts(orderBy, order, limit, offset); 

    } 
} 
+4

Questo mi ha fatto impazzire tutta la notte, specialmente la mancanza di documentazione. @krdx la tua risposta è completa ed estremamente utile, grazie. se possibile, potresti spiegare perché '@Bind (" foo ") String foo' non sostituirà la stringa nella query usando': foo'? o se hai trovato documentazione che copre questo? –

+2

Questa risposta mi ha salvato anche la vita. ''org.antlr: stringtemplate: 3.2.1'' funziona anche per me ma l'ultimo' 4.0.2' no. Posso chiedere perché questa annotazione '@ UseStringTemplate3StatementLocator' ha bisogno di tale dipendenza per farlo funzionare – DerekY

-1

bene si scopre che si aggiunge ORDER BY alla tua richiesta in questo modo

@SqlQuery("SELECT * FROM incident_events WHERE incident_id=:incidentId ORDER BY event_time DESC LIMIT :limit OFFSET :offset") 
List<IncidentEvent> getPaginated(@Bind("incidentId") int incidentId, @Bind("limit") int limit, @Bind("offset") int offset); 
+0

Penso che fosse sulla scelta dinamica del campo che stai ordinando da – Bernhard

2

Penso che sia perché la libreria String modello si presume essere fornite e che non riesce assunto in fase di esecuzione. L'aggiunta in seguito all'applicazione POM dovrebbe risolvere il problema:

<dependency> 
    <groupId>org.antlr</groupId> 
    <artifactId>stringtemplate</artifactId> 
    <version>3.2.1</version> 
</dependency> 

Osservando JDBI 2 pom, viene visualizzata la seguente:

<dependency> 
    <groupId>org.antlr</groupId> 
    <artifactId>stringtemplate</artifactId> 
    <version>3.2.1</version> 
    <optional>true</optional> 
</dependency> 

Significato JDBI solito si lamentano sulla assenza di lib StringTemplate.