1-N 和 N-N 的检索策略
知识点
我在之前的博客中对 1-N 和 N-N 有过学习,所以我假设读者已经了解了 Hibernate 中关于 1-N 和 N-N 的映射。我们建立了 Customer 与 Order 的 1-N 关联关系,表示一个顾客可以有多个订单。
我们在映射文件中,用元素来配置 1-N 关联以及 N-N 关联关系。元素有 lazy、fetch 和 batch-size 属性。
- lazy: 主要决定 orders 集合被初始化的时机。即到底是在加载 Customer 对象时就被初始化,还是在程序访问 orders 集合时被初始化。
- fetch: 取值为 “select” 或 “subselect” 时,决定初始化 orders 的查询语句的形式;若取值为”join”, 则决定 orders 集合被初始化的时机
- 若把 fetch 设置为 “join”, lazy 属性将被忽略
- 元素的 batch-size 属性:用来为延迟检索策略或立即检索策略设定批量检索的数量. 批量检索能减少 SELECT 语句的数目, 提高延迟检索或立即检索的运行性能。
lazy 属性 (默认值 true) | fetch 属性 (默认值 select) | 检索策略 |
---|---|---|
true | 未设置 (取默认值 select) | 采用延迟检索策略,这是默认的检索策略,也是优先考虑使用的检索策略 |
false | 未设置 (取默认值 select) | 采用立即索策略,当使用 Hibernate 二级缓存时可以考虑使用立即检索 |
extra | 未设置 (取默认值 select) | 采用加强延迟检索策略,它尽可能的延迟 orders 集合被初始化的时机 |
true,extra or extra | 未设置 (取默认值 select) | lazy 属性决定采用的检索策略,即决定初始化 orders 集合的时机。fetch 属性为 select,意味 着通过 select 语句来初始化 orders 的集合,形式为 SELECT * FROM orders WHERE customer _id IN (1,2,3,4) |
true,extra or extra | subselect | lazy 属性决定采用的检索策略,即决定初始化 orders 集合的时机。fetch 属性为 subselect,意味 着通过 subselect 语句来初始化 orders 的集合,形式为 SELECT * FROM orders WHERE customer_id IN (SELECT id FROM customers) |
true | join | 采采用迫切左外连接策略 |
Lazy
我们现在开始研究一下关于元素的 lazy 属性。
Demo
首先我们看一下延迟检索,也就是属性的 lazy 为 true 或者不设置的情况下:
@Test
public void testOne2ManyLevelStrategy() {
Customer customer = (Customer) session.get(Customer.class, 1);
System.out.println(customer.getCustomerName());
System.out.println(customer.getOrders().getClass());
System.out.println(customer.getOrders().size());
}
下面是控制的输出结果
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_0_0_,
customer0_.CUSTOMER_NAME as CUSTOMER2_0_0_
from
CUSTOMERS customer0_
where
customer0_.CUSTOMER_ID=?
AA
class org.hibernate.collection.internal.PersistentSet
Hibernate:
select
orders0_.CUSTOMER_ID as CUSTOMER3_0_1_,
orders0_.ORDER_ID as ORDER_ID1_1_1_,
orders0_.ORDER_ID as ORDER_ID1_1_0_,
orders0_.ORDER_NAME as ORDER_NA2_1_0_,
orders0_.CUSTOMER_ID as CUSTOMER3_1_0_
from
ORDERS orders0_
where
orders0_.CUSTOMER_ID=?
3
从结果中可以明显的看出,Hibernate 使用了延迟检索。其中的 orders 并没有初始化,而是返回了一个集合代理对象。当我们通过 customer.getOrders().size() 这段代码真正要使用 orders 集合的时候,才发送 SQL 语句进行查询。
在延迟检索(lazy 属性值为 true)集合属性时,Hibernate 在以下情况下初始化集合代理类实例:
- 应用程序第一次访问集合属性: iterator(), size(), isEmpty(), contains() 等方法
- 通过 Hibernate.initialize() 静态方法显式初始化
下面我们将的 lazy 属性修改为 false,如 <set name="orders" table="ORDERS" inverse="true" lazy="false">
。
修改完之后再执行测试代码,输出结果也就很明显了,在调用 load() 方法时,会先执行 SQL 语句取出 Customer 以及相关联的 orders。
最后,提一下 lazy 的另一个取值 extra。 该取值与 true 类似,主要区别是增强延迟检索策略能够进一步延迟 Customer 对象的 orders 集合代理实例的初始化时机。
首先我们将元素中的 lazy 设为 extra。我们同样的执行上文中的单元测试代码, 得到以下结果:
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_0_0_,
customer0_.CUSTOMER_NAME as CUSTOMER2_0_0_
from
CUSTOMERS customer0_
where
customer0_.CUSTOMER_ID=?
AA
class org.hibernate.collection.internal.PersistentSet
Hibernate:
select
count(ORDER_ID)
from
ORDERS
where
CUSTOMER_ID =?
3
我们观察第二个 SQL 语句。我们发现他并没有对 orders 进行初始化,而是通过使用一个 count() 函数。extra 取值为增强的延迟检索,该取值会尽可能的延迟集合初始化的时机。
例如:当我们将 lazy 设置为 true(延迟检索),而我们调用 order.size() 方法的时候,这个时候就会通过 SQL 将 orders 集合初始化。但现在我们用 extra 这个属性,发现我们调用 orders 的 size 方法,并没有初始化,而是通过了一个 count 函数。
所以我们得到结论:
增强延迟检索策略能进一步延迟 Customer 对象的 orders 集合代理实例的初始化时机:
- 当程序第一次访问 order 属性的 size(), contains() 和 isEmpty() 方法时, Hibernate 不会初始化 orders 集合类的实例,仅通过特定的 select 语句查询必要的信息,不会检索所有的 Order 对象。
- 当程序第一次访问 orders 属性的 iterator() 方法时,会导致 orders 集合代理类实例的初始化。(这个是没办法的= =)
但其实我们在实际的开发过程中,当我们要用到 size 或者 contains 等方法的时候,基本上代表我们就要用到集合部分的属性。如果我们选用 extra 的话,反倒会多发送 SQL 语句。
关于 extra 的其他点大家可以自己进行一些测试,比较简单方便。
BatchSize
元素有一个 batch-size 属性,用来为延迟检索策略或立即检索策略设定批量检索的数量。批量检索能减少 SELECT 语句的数目,提高延迟检索或立即检索的运行性能。 ### Demo 首先说一下这个 Demo 中用到的数据的数据,Customers 表中共有 4 条数据。而我们的 lazy 属性设为 true,即采用延迟加载策略。 我们看一下下面的代码: ```java @Test public void testSetBatchSize() { List customers = session.createQuery("FROM Customer").list(); System.out.println(customers.size()); for (Customer customer : customers) { if (customer.getOrders() != null) System.out.println(customer.getOrders().size()); } } ``` 首先,在该代码的第三行我们获取了所有的 Customer,得到 list 集合(集合中存的是 Customer 对象),然后遍历该 list 集合;每次循环,我们都通过 customer.getOrders 方法来实例化 orders 集合。 篇幅原因,我在这不贴控制台的输出了。但可以想到的是,会一共发出 5 条 SQL。第一条是获取 customer 集合,因为集合有 4 个 customer 对象,所以会再发出 4 条 SQL 来分别初始化。 好了,现在我们修改元素,在里边加入`batch-size="4"`,所以现在的元素的代码为``。 我们重新运行单元测试代码,得到结果: ``` Hibernate: select customer0_.CUSTOMER_ID as CUSTOMER1_0_, customer0_.CUSTOMER_NAME as CUSTOMER2_0_ from CUSTOMERS customer0_ 4 Hibernate: select orders0_.CUSTOMER_ID as CUSTOMER3_0_1_, orders0_.ORDER_ID as ORDER_ID1_1_1_, orders0_.ORDER_ID as ORDER_ID1_1_0_, orders0_.ORDER_NAME as ORDER_NA2_1_0_, orders0_.CUSTOMER_ID as CUSTOMER3_1_0_ from ORDERS orders0_ where orders0_.CUSTOMER_ID in ( ?, ?, ?, ? ) 3 3 3 0 ``` 我们看到这个时候只有 2 条 SQL 语句。第一条同样的还是得到 customer,而第二条 SQL 语句直接全部初始化了 4 个 orders 集合。这就是 batch-size 的作用所在。我们可以想到的是,如果我们将 batch-size 设为 2,则是 3 条 SQL 语句。**也就是我们上文中提到的批量检索。** ## Fetch 元素的 fetch 属性是用于确定初始化 orders 集合的方式。 1. 默认值为 orders,也就是通过正常的方式来初始化 set 元素。例如我们在将 batch-size 例子的时候,我们并没有设置 fetch 属性(默认即为 select),所以我们在初始化 orders 集合的时候,会发现在 SQL 语句中是通过`where orders0_.CUSTOMER_ID in (?, ?, ?, ?)`这种方式来进行初始化的。 2. 可以取值为 subselect,我们看名字也能知道大概意思,就是通过子查询的方式来初始化所有的 set 集合。例如我们设置为 subselect 后,可看到 SQL 语句中包含`where orders0_.CUSTOMER_ID in (select customer0_.CUSTOMER_ID from CUSTOMERS customer0_)`。**子查询作为 WHERE 子句的 in 的条件出现的,子查询查询 1 的一端的 ID,此时 lazy 属性是有效的,但 batch-size 属性失效。** 3. 若取值为 join: + 在加载 1 的一端的对象的时候,使用迫切左外连接(可参考该博客进行学习[Hibernate:深入 HQL 学习](http://tracylihui.github.io/2015/07/08/Hibernate%EF%BC%9A%E6%B7%B1%E5%85%A5HQL%E5%AD%A6%E4%B9%A0/))的方式检索 N 的一端的集合的属性; + 忽略 lazy 属性(理解了迫切左外连接,也就能知道为啥会忽略 lazy 属性了); + HQL 查询忽略`fetch="join"`的取值; + **Query 的 list() 方法会忽略映射文件中配置的迫切左外连接检索策略,而依旧采用延迟加载策略。(这个点之前测试的时候老是忽略掉了)** # 总结 以上就是关于 Hibernate 检索策略的学习,但并不全。将在下一篇中,对 N-1 和 1-1 的检索策略进行学习,并做一个总结。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论