Динамический порядок в запросах объектов JDBI SQL

Как вы упорядочиваете запросы объектов SQL в JDBI?

Я хочу сделать что-то вроде:

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

or

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

person sclausen    schedule 28.05.2013    source источник


Ответы (3)


Недавно я изучал DropWizard, который поставляется в комплекте с JDBI, и быстро столкнулся с той же проблемой. К сожалению, у JDBI слабая документация (JavaDoc и некоторые примеры модульных тестов в его репозитории git не справляются с ней сами по себе), что разочаровывает.

Вот что я обнаружил, что обеспечивает динамический порядок в Sql Object API для JDBI на основе моего образца 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 — эта аннотация позволяет нам использовать синтаксис <arg> в запросах. Эти аргументы будут заменены любым значением, которое мы указываем через аннотацию @Define.

Чтобы иметь возможность использовать эту функцию, мне пришлось дополнительно добавить эту зависимость в мой файл pom.xml:

<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 Следует отметить, что это открывает нам доступ к Sql Injection, поскольку значения вставляются непосредственно в запрос. (В отличие от синтаксиса :arg в запросе и аннотации @Bind, которая использует подготовленные операторы и защищает от внедрения sql). По крайней мере, вы должны очистить параметры, которые будут использоваться для полей @Define. (Простой пример для DropWizard ниже).

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

  }
}
person krdx    schedule 05.10.2013
comment
Это сводило меня с ума всю ночь, особенно отсутствие документации. @krdx ваш ответ тщательный и чрезвычайно полезный, спасибо. если возможно, не могли бы вы объяснить, почему @Bind("foo") String foo не будет подставлять строку в запрос, используя :foo ? или если вы нашли документацию, которая охватывает это? - person K Raphael; 15.05.2015
comment
Этот ответ тоже спас мне жизнь. 'org.antlr:stringtemplate:3.2.1' у меня тоже работает, а последняя 4.0.2 нет. Могу я спросить, почему эта аннотация @UseStringTemplate3StatementLocator нуждается в такой зависимости, чтобы заставить ее работать - person macemers; 19.04.2016
comment
Что, если мой порядок, порядок, лимит, смещение исходят от объекта (скажем, Page). Например: dao.getProducts(Page page). Как я могу @Define их в этом случае? - person shane; 16.11.2018

Я думаю, это потому, что предполагается, что библиотека строковых шаблонов предоставлена, и это предположение не работает во время выполнения. Добавление следующего в приложение POM должно решить проблему:

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

Глядя на JDBI 2 pom, вы можете увидеть следующее:

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

Это означает, что JDBI не будет жаловаться на отсутствие библиотеки stringtemplate.

person Макс Нікітін    schedule 24.10.2016

ну получается, что вы добавляете ORDER BY в свой запрос вот так

@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);
person austiine    schedule 04.03.2015
comment
Я думаю, речь шла о динамическом выборе поля, по которому вы заказываете - person Bernhard; 18.08.2015