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 上指定 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();