2013-05-28 5 views
12

Wie funktioniert das Sortieren mit SQL Object Queries in JDBI?Dynamische Reihenfolge in JDBI-SQL-Objektabfragen

Ich möchte wie etwas tun:

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

oder

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

Antwort

26

habe ich vor kurzem DropWizard erforscht, die mit JDBI kommt gebündelt und kam schnell über das gleiche Problem. Leider hat JDBI eine lustlose Dokumentation (JavaDoc und einige Sample-Unit-Tests auf seinem git-Repository machen es nicht alleine), was enttäuschend ist.

Hier ist, was ich herausgefunden, dass erzielt eine dynamische Ordnung in einem Objekt-API SQL-Code für JDBI basiert auf meiner Probe 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 - diese Anmerkung ist das, was uns die <arg> Syntax verwenden können in die Abfragen. Diese Argumente werden durch einen beliebigen Wert ersetzt, den wir über die @Define Annotation bereitstellen.

Um in der Lage sein, diese Funktion, die ich zusätzlich diese Abhängigkeit zu meiner pom.xml Datei hinzufügen musste verwenden:

<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-WARNUNG Es sollte beachtet werden, dass diese uns da die Werte bis zu Sql Injection eröffnet werden direkt in die Abfrage eingefügt. (Im Gegensatz zu :arg Syntax in der Abfrage und @Bind Annotation, die vorbereitete Anweisungen verwendet und schützt gegen SQL-Injektion). Zumindest sollten Sie die Parameter bereinigen, die für die Felder @Define verwendet werden. (Einfaches Beispiel für DropWizard unten).

@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

Das hat mich die ganze Nacht verrückt gemacht, vor allem der Mangel an Dokumentation. @ krdx Ihre Antwort ist gründlich und sehr hilfreich, danke. Wenn möglich, könnten Sie erklären, warum '@Bind ("foo") String foo' den String nicht mit ': foo' in die Abfrage einfügt? oder wenn Sie eine Dokumentation gefunden haben, die dies abdeckt? –

+2

Diese Antwort rettet mein Leben auch. ''org.antlr: singtemplate: 3.2.1'' funktioniert auch für mich, aber die neueste' 4.0.2' nicht. Darf ich fragen, warum diese Annotation '@ UseStringTemplate3StatementLocator' eine solche Abhängigkeit benötigt, damit sie funktioniert – DerekY

-1

gut es stellt sich heraus, dass Sie die ORDER BY hinzufügen, um Ihre Abfrage wie so

@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

Ich denke, es ging um die dynamische Auswahl des Feldes, das Sie gerade bestellen – Bernhard

2

Ich denke sein, weil die String Template-Bibliothek angenommen wird, zur Verfügung gestellt werden und diese Annahme nicht zur Laufzeit. POM Anschluss an die Anwendung Hinzufügen sollte das Problem beheben:

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

Mit Blick auf JDBI 2 pom, können Sie sehen folgendes:

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

Bedeutung JDBI wird nicht auf die Abwesenheit der String lib beschweren.