SqlQuery

使用 SqlQuery,我们可以在没有任何实体 Bean 或 dto Bean 的情况下执行 sql 查询。相反,我们可以

  • 直接读取 JDBC ResultSet
  • 作为 SqlRow 对象读取
  • 使用 mapToScalar() 并读取简单的标量值,如 Long、BigDecimal、OffsetDateTime 等
  • 使用 mapTo()RowMapper 映射和读取对象

 

使用 SqlRow 的 findOne 示例
String sql = "select id, name, when_active from customer where id = :id"

SqlRow row = DB.sqlQuery(sql)
    .setParameter("id", 42)
    .findOne();

String name = row.getString("name");
Timestamp active = row.getTimestamp("when_active");

 

使用 ResultSet 的 findEachRow 示例
String sql = "select id, name, status from customer order by name desc";

DB.sqlQuery(sql)
  .findEachRow((resultSet, rowNum) -> {

    long id = resultSet.getLong(1);
    String name = resultSet.getString(2);
    ...
  });
mapToScalar() 示例
String sql = "select mysequence.nextval";

Long nextVal = DB.sqlQuery(sql)
  .mapToScalar(Long.class)
  .findOne();

使用 ? 绑定位置参数

位置参数使用 ? 作为占位符。

String sql = "select id, name, when_active from customer where status = ? and when_created > ?";

List<SqlRow> rows = DB.sqlQuery(sql)
    .setParameter(1, "NEW")
    .setParameter(2, lastWeek)
    .findList();

我们可以省略索引位置,如下一个示例所示。

String sql = "select id, name, when_active from customer where status = ? and when_created > ?";

List<SqlRow> rows = DB.sqlQuery(sql)
    .setParameter("NEW")
    .setParameter(lastWeek)
    .findList();

我们还可以使用 setParameters() 设置多个位置参数。

String sql = "select id, name, when_active from customer where status = ? and when_created > ?";

List<SqlRow> rows = DB.sqlQuery(sql)
    .setParameters("NEW", lastWeek)
    .findList();

使用 :name 绑定命名参数

命名参数使用 :foo 形式作为占位符。命名参数的优点在于我们可以使用它们将集合绑定到 IN 表达式中,并且同一个参数可以在 SQL 中出现多次。

使用命名参数绑定
String sql =
  "select id, name, when_active " +
  "from customer " +
  "where status = :status and when_created > :created";

List<SqlRow> rows = DB.sqlQuery(sql)
    .setParameter("status", "NEW")
    .setParameter("created", lastWeek)
    .findList();

绑定集合

要将值集合绑定到 IN 子句中,我们需要使用命名参数或索引位置参数。

命名参数示例 (:names)
String sql = "select c.id, c.name from customer c where c.name in (:names)";

List<SqlRow> rows = DB.sqlQuery(sql)
  .setParameter("names", asList("Rob", "Fiona", "Jack"))
  .findList();
索引参数示例 - ?2
String sql = "select c.id, c.name from customer c " +
  "where c.status = ? and c.name in (?2) and c.when_created > ?";

List<SqlRow> rows = DB.sqlQuery(sql)
  .setParameter("NEW")
  .setParameter(asList("Rob", "Fiona", "Jack"))
  .setParameter(lastWeek)
  .findList();

Postgres ANY

使用 Postgres 时,我们可以在 SqlQuery 中使用 Postgres ANY 和位置参数。

使用 Postgres ANY 的好处在于,无论参数数量多少,我们最终都会使用一个 SQL 查询 - 一个 JDBC PreparedStatement,并且数据库只有一个 SQL 查询要解析。这导致数据库执行更少的硬解析,更好地利用 PreparedStatement 缓存,减少要缓存的查询计划,减少消耗的内存。

示例

使用索引位置参数并绑定一个将绑定为 JDBC ARRAY 的集合时。

List<Integer> ids = List.of(1, 2);

List<SqlRow> list = DB.sqlQuery("select id, name from o_customer where id = any(?)")
    .setParameter(1, ids) // bind as JDBC ARRAY
    .findList();

在使用 Postgres 时,我们通常更喜欢使用它而不是使用类似于以下示例的 IN 子句。我们更喜欢 ANY 的原因是,无论我们在 ARRAY 中有多少个元素,我们最终都会得到一个 SQL 语句。这为我们提供了一个查询计划、一个 PrepareStatement,并且在数据库端,无论我们绑定了多少个 id 值,都只有一个要解析的查询语句。

与使用 IN 子句相比,如果我们有 3 个 id,那么它是一个不同的 SQL 查询,而另一个有 4 个 id,例如 - 我们最终会得到更多不同的 PreparedStatements,可能更多,具体取决于我们有多少个 id 值。

List<SqlRow> list = DB.sqlQuery("select id, name from o_customer where id in (:idList)")
    .setParameter("idList", ids) // bind parameter expansion
    .findList();

当我们有不同的 id 值时,使用 IN 子句将导致不同的 SQL 查询。当提供 4 个 id 值时,in (:idList) 会变成 SQL in (?, ?, ?, ?)

firstRow / maxRows

我们可以在 SqlQuery 上指定 firstRowmaxRows,Ebean 会根据数据库平台修改 SQL 以添加适当的行限制子句。

String sql = "select id, name, when_active from customer where status = ?";

List<SqlRow> rows = DB.sqlQuery(sql)
    .setParameter(1, "NEW")
    .setFirstRow(10)
    .setMaxRows(10)
    .findList();

mapToScalar() - 单列标量结果

当查询在 select 子句 中只有一列时,使用 mapToScalar()。这指定了用于读取结果的类型。

String sql = "select mysequence.nextval";

Long nextVal = DB.sqlQuery(sql)
  .mapToScalar(Long.class)
  .findOne();
String sql = "select max(unit_price) from order_lines where order_qty > ?";

BigDecimal maxPrice = DB.sqlQuery(sql)
  .setParameter(42)
  .mapToScalar(BigDecimal.class)
  .findOne();
String sql = "select max(order_date) from orders where customer_id = ?";

OffsetDateTime maxDate = DB.sqlQuery(sql)
  .setParameter(42)
  .mapToScalar(OffsetDateTime.class)
  .findOne();

Ebean 支持的所有标量类型都可以使用 - 请参阅 映射类型

mapTo() - RowMapper

我们可以实现一个 RowMapper 来将 ResultSet 转换为 dto bean。

请注意,这类似于 DtoQuery,不同之处在于这里的映射是显式的,而使用 DtoQuery,映射实际上是基于命名约定映射的自动(无需任何操作)。从这个意义上说,预期人们会先使用 DtoQuery,只有在必要时才使用这种显式的 RowMapping。

static class CustomerDtoMapper implements RowMapper<CustomerDto> {

  @Override
  public CustomerDto map(ResultSet resultSet, int rowNum) throws SQLException {

    long id = resultSet.getLong(1);
    String name = resultSet.getString(2);
    String status = resultSet.getString(3);

    return new CustomerDto(id, name, status);
  }
}

...
static final CustomerDtoMapper CUSTOMER_MAPPER = new CustomerDtoMapper()

 

然后我们可以使用它来返回 DTO bean。

String sql = "select id, name, status from customer where name = ?";

List<CustomerDto> rob = DB.sqlQuery(sql)
  .setParameter("Rob")
  .mapTo(CUSTOMER_MAPPER)
  .findList();