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();
使用上述查询,我们有customer和customer.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 join
或join
)基于关系的基数和可选项。例如,如果订单上的客户外键可为 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,但这需要更大的跳跃,而原始表达式为我们提供了这种灵活性。
我不确定是否允许我选择最喜欢的功能,但如果允许的话,我会投票给原始表达式。