Where

查询 Bean 属性提供了一种很好的类型安全方式,可将表达式添加到 where 子句。我们可以对查询路径、表达式和绑定值类型进行编译时验证。

// using query bean
Customer rob =
  new QCustomer()
    .name.equalTo("Rob") // type safe expression with IDE auto-completion
    .findOne();

可以使用不使用查询 Bean 的方式编写相同的查询,如下所示

// using standard query
Customer rob = DB.find(Customer.class)
    .where().eq("name", "Rob")
    .findOne();

以上两个查询生成相同的 sql

select ...
from customer t0
where t0.name = ?

使用以上方法,name 属性的类型为 String/varchar,因此只允许有效的 String 表达式,例如 like、startsWith、contains 等,并且表达式的绑定值必须为 String。

使用查询 Bean 创建查询时,开发人员可以获得 IDE 帮助/自动完成,因为 IDE 根据类型自动完成属性和表达式。

此外,使用查询 Bean,如果重命名属性(例如,“name”变为“fullName”)或如果属性更改其类型,则查询将无法再编译。

路径

对于与 Bean 关联的属性(OneToOne、OneToMany、ManyToOne、ManyToMany),我们可以“导航”这些路径,添加有效的表达式。

例如,Customer 的路径“billingAddress.city”
// using query beans
List<Customer> customers =
  new QCustomer()
    .billingAddress.city.equalTo("Auckland")
    .findList();
// using standard query
List<Customer> customers = DB.find(Customer.class)
  .where()
     .eq("billingAddress.city", "Auckland")
  .findList();

Ebean 将自动添加适当的 SQL JOINS 以支持查询中的表达式。

select ...
from customer t0
join address t1 on t1.id = t0.billing_address_id
where t1.city = ?

这些属性路径可以是任何深度。在下面的示例中,路径通过customer.billingAddress将我们从 Order bean 带到 Customer bean 再到 Address bean。

例如,Order 的路径“customer.billingAddress.city”
List<Order> orders =
  new QOrder()
    .customer.billingAddress.city.equalTo("Auckland")
    .findList();

AND

默认情况下,多个表达式通过AND添加。

List<Customer> customers =
  new QCustomer()
    .status.equalTo(Status.NEW)
    .whenCreated.greaterThan(lastWeek)
    .findList();
select ...
from customer t0
where t0.status = ? and t0.when_created > ?
// using standard query
List<Customer> customers = DB.find(Customer.class)
  .where()
    .eq("status", Status.NEW)
    .gt("whenCreated", lastWeek)
    .findList();
例如,Order 的多个路径
List<Order> orders =
  new QOrder()
    .orderDate.greaterThan(lastWeek)
    .customer.status.equalTo(Status.NEW)
    .customer.billingAddress.city.equalTo("Auckland")
    .findList();

使用上述查询,我们有customercustomer.billingAddress的附加路径。Ebean 将确定支持表达式所需的联接。

select ...
from orders t0
join customer t1 on t1.id = t0.order_id
join address t2 on t2.id = t1.billing_address_id
where t0.order_date >= ? and t1.status = ? and t2.city = ?

 

有关联接类型的注释

使用的联接类型(left joinjoin)基于关系的基数和可选项。例如,如果订单上的客户外键可为 null,则这是一个可选关系,并且使用了left join

对于任何路径,如果“更高级别”联接是left join,则所有“子联接”(对于该路径)也必须是left join(即“left join 级联”)。例如,在上面的示例中 - 如果从订单到客户的联接是 left join,则从客户到地址的联接也必须是 left join。

ToMany路径上对表达式使用联接的类型取决于表达式是否包含在OR中。如果表达式包含在OR中,则使用left join

OR

当我们希望通过OR添加多个表达式时,我们使用or()endOr(),并且它们之间的所有表达式都由OR联接。

例如:(name is null OR name = 'Rob')

Customer customer
  = new QCustomer()
     .or()
       .name.isNull()
       .name.equalTo("Rob")
     .endOr()
     .findOne()
select ...
from customer t0
where (t0.name is null or t0.name = ? )
Customer customer = DB.find(Customer.class)
   .where()
     .or()
       .isNull("name")
       .eq("name", "Rob")
     .endOr()
     .findOne()
或使用原始表达式

与其使用or()endOr()的流畅风格,我们还可以使用raw()表达式。例如

Customer customer
  = new QCustomer()
     .raw("(name is null or name = ?)", "Rob")
     .findOne()

原始表达式

有时我们希望向 where 子句添加原始表达式。我们使用raw()将任意 SQL、函数和存储过程包含到查询where 子句中。

例如,简单的原始表达式
List<Order> orders =
  new QOrder()
  .raw("orderDate > shipDate ")
  .findList()
例如,使用 sql 函数
List<Order> orders =
  new QOrder()
  .raw("add_days(orderDate, 10) < ?", someDate)
  .findList();
例如,sql 子查询
List<Order> orders =
  new QOrder()
   .status.equalTo(Status.NEW)
   .raw("t0.customer_id in (select customer_id from customer_group where group_id = any(?::uuid[]))", groupIds)
   .findList()

 

属性路径到 sql 联接

如果我们在 raw() 中使用属性路径,则 Ebean 将自动添加适当的联接来支持表达式。例如

List<Order> orders =
    new QOrder()
      .raw("(customer.name = ? or customer.billingAddress.city = ?)", "Rob", "Auckland")
      .findList();

... 生成以下 SQL,其中联接已添加到 customer 和 address 表中。

select t0.id, t0.status, t0.order_date, t0.ship_date, ...
from orders t0
left join customer t1 on t1.id = t0.customer_id            -- supports customer.name
left join address t2 on t2.id = t1.billing_address_id      -- supports customer.billingAddress.city
where (t1.name = ? or t2.city = ?); --bind(Rob,Auckland)

 

梳理表达式

我们可以将raw()表达式与其他表达式结合使用。例如

List<Order> orders =
  new QOrder()
    .status.eq(Order.Status.NEW)       // before raw()
    .raw("(customer.name = ? or customer.billingAddress.city = ?)", "Rob", "Auckland")
    .orderDate.greaterThan(lastWeek)   // after raw()
    .findList();
select ...
where t0.status = ?  and (t1.name = ? or t2.city = ?) and t0.order_date > ?
 --bind(NEW,Rob,Auckland,2019-01-17)

 

原始表达式的颂歌

如果开发人员的生活很简单,我们就不需要原始表达式。许多实际项目通常有一些查询,其中我们漂亮的 ORM 查询可以让我们完成 90%,但需要一个最好用 SQL 表达或使用特定于数据库的函数或特性的谓词。

原始表达式是这个很不错的功能,它允许我们将任意 SQL 表达式放入 where 子句中。我们也可以转而查找 findNative 等并提供查询的所有 SQL,但这需要更大的跳跃,而原始表达式为我们提供了这种灵活性。

我不确定是否允许我选择最喜欢的功能,但如果允许的话,我会投票给原始表达式。