Fetch
使用 fetch,我们可以指定应在关联 Bean 中获取的属性。通过 OneToOne、OneToMany、ManyToOne 和 ManyToMany 关联的 Bean。
Fetch 表示我们希望查询急切加载该路径,并且我们更希望以 SQL JOIN 的方式完成此操作。
QCustomer cust = QCustomer.alias();
QContact cont = QContact.alias();
List<Customer> customers =
new QCustomer()
.select(cust.name, cust.version, cust.whenCreated) // root level properties
.contacts.fetch(cont.email) // contacts is a OneToMany path
.name.istartsWith("Rob")
.findList();
生成的 sql 为
select t0.id, t0.name, t0.version, t0.when_created, -- customer columns
t1.id, t1.email -- contact columns
from customer t0
left join contact t1 on t1.customer_id = t0.id
where lower(t0.name) like ? escape'|' order by t0.id
contacts 路径是 OneToMany,因此我们获得到 contact 表的 left join。我们仅获取 contact 的 id 和 email 属性。
使用查询表达式 API 编写的上述查询为
List<Customer> customers =
database.find(Customer.class)
.select("name, version, whenCreated") // root level properties
.fetch("contacts", "email") // contacts is a OneToMany path
.where()
.istartsWith("name", "Rob")
.findList();
Fetch 变体 - fetch、fetchQuery、fetchCache、fetchLazy
fetch 有 4 种变体。它们的摘要如下
- fetch - 急切获取,优先使用 SQL JOIN
- fetchQuery - 使用单独的辅助 SQL 查询急切获取
- fetchCache - 急切获取,命中 L2 缓存,缓存未命中使用单独的辅助 SQL 查询获取
- fetchLazy - 延迟获取
Ebean 在将 ORM 查询转换为 SQL 查询时应用 规则,以便
- Ebean 不会生成 SQL 笛卡尔积
- Ebean 将遵守 SQL 中的 maxRows
Ebean 会自动将 fetch 路径转换为 fetchQuery 路径,以确保遵守这些规则。实际上,可以包含在 SQL 查询中的 OneToMany 和 ManyToMany 路径的数量有限(否则我们会得到一个 SQL 笛卡尔积,这是不可取的)。
FetchQuery
当我们指定 fetchQuery 时,我们告诉 Ebean 我们希望急切获取此路径,但我们希望使用单独的 SQL 查询(称为“辅助查询”或“查询联接”)来完成此操作。
我们选择在特定路径上执行此操作,因为我们知道基于基数和结果的相对宽度等因素,SQL 联接与查询联接的相对成本。
List<Order> orders =
new QOrder()
.customer.fetchQuery() // fetch customer (and children) using a "secondary query"
.customer.billingAddress.fetch()
.customer.shippingAddress.fetch()
.findList();
使用上述查询,我们使用 fetchQuery
显式获取客户及其账单和送货地址,并使用第二个单独的 SQL 查询。
主查询
-- "primary" query fetches the orders only
select t0.id, ...
from orders t0
辅助查询(又称“查询联接”)
-- "secondary" query fetches customers with
-- their billing and shipping addresses
select t0.id, t0.inactive, t0.name, ..., -- customer
t1.id, t1.line1, t1.line2, t1.city, ..., -- customers billing address
t2.id, t2.line1, t2.line2, t2.city, ... -- customers shipping address
from customer t0
join address t1 on t1.id = t0.billing_address_id
join address t2 on t2.id = t0.shipping_address_id
where t0.id in (?, ?, ... ) -- customer ids
FetchCache
fetchCache 类似于 fetchQuery,只不过我们首先命中 L2 缓存。
List<Order> orders =
new QOrder()
.customer.fetchCache() // Hit L2 cache for customer, for cache miss load from database
.findList();
在上述查询中,查询从数据库中获取订单。对于 Customer,它将命中 Customer 的 L2 缓存,对于缓存未命中,它将从数据库加载 Customer。此操作一次以 100 个客户为一批执行。
FetchLazy
使用 fetchLazy,我们不想急切获取此路径,但如果它被延迟加载,我们正在优化延迟加载查询将获取的内容。
例如,延迟加载 Customer 仅获取名称。
List<Order> orders =
new QOrder()
.fetchLazy("customer", "name")
.findList();
...
// invoke lazy loading will only fetch customer name
customer.getName();
例如,延迟加载 Customer 另外获取 billingAddress 和 shippingAddress。
QCustomer cust = QCustomer.alias();
List<Order> orders =
new QOrder()
.fetchLazy("customer")
.fetch("customer.billingAddress") // fetched with lazy load of customer
.fetch("customer.shippingAddress") // fetched with lazy load of customer
.findList();
...
// invoke lazy loading will fetch customer name + billingAddress + shippingAddress
customer.getName();
别名查询 Bean
使用查询 Bean 时,我们使用 alias
Bean 为 select 和 fetch 提供属性。
// "alias" beans for customer and contact which
// we use to specify the properties to select and fetch
QCustomer cust = QCustomer.alias();
QContact con = QContact.alias();
// using query beans
List<Customer> customers =
new QCustomer()
.select(cust.name, cust.version, cust.whenCreated) // customer properties only
.contacts.fetch(con.email) // contact properties only
.name.istartsWith("Rob")
.findList();
Kotlin 别名查询 Bean
对于 Kotlin,alias Bean 是查询 Bean 的伴生对象,可通过 _alias
访问。否则,查询在 Kotlin 中是相同的。
val cust = QCustomer._alias
val con = QContact._alias
Select + Fetch = 投影查询调优
使用 select 和 fetch,我们实际上正在调优查询,控制加载对象图的哪一部分。
通常,我们希望查询仅从数据库加载它需要的内容。这使我们能够降低查询成本、减少获取并通过网络拉取的数据量,以及使用覆盖索引等。
select -> “图形根 级属性”
select 定义在 对象图的根级别 获取什么。
fetch -> “图形叶 属性”
fetch 定义为 对象图的叶 获取什么。这转化为 在对象图的 OneToOne、OneToMany、ManyToOne 和 ManyToMany 路径上获取什么。
请注意,FetchGroup 提供了 select + fetch 的替代方案。
ORM 查询到 SQL 查询规则
Ebean 在将 ORM 查询转换为 SQL 查询时会强制执行 2 条规则。这些规则会限制在将 ORM 查询转换为 SQL 查询时允许的 SQL 连接数。这两条规则是
- Ebean 不会生成 SQL 笛卡尔积
- Ebean 将遵守 SQL 中的 maxRows
规则 1:无 SQL 笛卡尔积
这条规则暗示 Ebean 最多允许 1 个连接到 OneToMany
路径或 ManyToMany
路径。如果 ORM 查询包含多个此类路径,则 ORM 查询将被转换为多个 SQL 查询并执行。
我们避免 SQL 笛卡尔积,因为它极有可能产生非常糟糕的 SQL 查询。
规则 2:始终在 SQL 中遵守 maxRows
这条规则暗示如果存在 maxRows
,Ebean 不能包含任何连接到 OneToMany
路径或 ManyToMany
路径。这是因为 SQL max rows 作用于行,所以我们不能包含基数大于 1 的路径的 SQL 连接。
遵守 SQL 中的 maxRows 非常重要,因为数据库通常可以为我们提供更好的查询计划和更高效的 SQL 查询。
Ebean 检测哪些路径是“ToMany”路径,并确定是否需要将其中任何路径从获取路径转换为获取查询路径。Ebean 首先考虑 @FetchPreference
映射,然后考虑在查询中指定路径的顺序。也就是说,通常第一个 ToMany 路径保持为获取,而其他 ToMany 路径转换为获取查询。