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文になるからです。これにより、単一のクエリプラン、単一のPreparedStatement、そしてデータベース側では、バインドするid値の数に関係なく、解析する単一のクエリ文が得られます。

`IN`句を使用する場合、3つのidがある場合は、4つのidがあるものとは異なるSQLクエリになります。たとえば、より多くの異なるPreparedStatementが生成され、id値の数によっては非常に多くなる可能性があります。

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

`IN`句を使用すると、id値の数によって異なるSQLクエリが生成されます。4つのid値が提供されると、`in (:idList)`はSQL `in (?, ?, ?, ?)`に変換されます。

firstRow / maxRows

SqlQueryで*firstRow*と*maxRows*を指定すると、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();