Hibernate的检索策略和优化

时间:2023-01-31 18:39:11

一、检索策略概述

当我们实现了一对多或者多对多的映射后,在检索数据库时需要注意两个问题:

1、使用尽可能小的内存:当 Hibernate 从数据库中加载一个客户信息时, 如果同时加载所有关联这个客户的订单信息, 而程序实际上仅仅需要访问客户信息, 那么这些关联的 订单信息就白白浪费了许多内存。

2、更高的查询效率:发送尽可能少的 SQL 语句。

二、类级别的检索策略(class标签中设置)

类级别可选的检索策略包括立即检索和延迟检索, Hibernate默认采用延迟检索。

类级别的检索策略可以通过 <class> 元素的 lazy 属性进行设置。

若 <class> 元素的 lazy 属性为 true 或取默认值, Session 的 load() 方法不会执行查询数据表的 SELECT 语句, 仅返回代理类对象的实例, 该代理类实例有如下特征:

1、由 Hibernate 在运行时采用 CGLIB 工具动态生成。

2、Hibernate 创建代理类实例时, 仅初始化其 OID 属性

3、在应用程序第一次访问代理类实例的非 OID 属性时, Hibernate 会初始化代理类实例。

如果程序加载一个对象的目的是为了访问它的属性, 可以采取立即检索。如果程序加载一个持久化对象的目的是仅仅为了获得它的引用, 可以采用延迟检索。会出现懒加载异常!(获取具体属性之前session关闭了就会出现)。

注意:无论 <class> 元素的 lazy 属性是 true 还是 false, 只是针对load()方法设置有效。Session 的 get() 方法及 Query 的 list() 方法在类级别总是使用立即检索策略。

配置形式:

<class name="Customer" table="CUSTOMERS" lazy="false">

二、一对多的检索策略(set标签中设置)

在映射文件中, 用 <set> 元素来配置一对多关联及多对多关联关系.。<set> 元素有 lazy 和 fetch 属性,还有一个batch-size 属性。

lazy: 主要决定集合被初始化的时机。

fetch: 取值为 “select” 或 “subselect” 时, 决定初始化集合的查询语句的形式;  若取值为”join”, 则决定集合被初始化的时机。若把 fetch 设置为 “join”, lazy 属性将被忽略。

<set> 元素的 batch-size 属性:用来为延迟检索策略或立即检索策略设定批量检索的数量。批量检索能减少 SELECT 语句的数目, 提高延迟检索或立即检索的运行性能。

1、lazy属性

在延迟检索(lazy 属性值为 true) 集合属性时, Hibernate 在以下情况下初始化集合代理类实例 。

1)应用程序第一次访问集合属性: iterator(), size(), isEmpty(), contains() 等方法。

2)通过 Hibernate.initialize() 静态方法显式初始化。

增强延迟检索(lazy 属性为 extra): 与 延迟检索类似,主要区别是增强延迟检索策略能进一步延迟 Customer 对象的 orders 集合代理实例的初始化时机。具体来说

1)当程序第一次访问集合属性的 iterator() 方法时, 会导致集合代理类实例的初始化。

2)当程序第一次访问集合属性的 size(), contains() 和 isEmpty() 方法时, Hibernate 不会初始化集合类的实例, 仅通过特定的 select 语句查询必要的信息。

推荐使用默认值lazy=true即延迟检索的方式。

2、fetch属性

1)默认值为 select,即通过正常的方式来初始化 set 集合元素。

2)可以取值为 subselect,通过子查询的方式来初始化所有的 set 集合。子查询作为 where 子句的 in 的条件出现, 子查询查询的是所有 1 的那一端的 ID,此时 lazy 设置有效,但 batch-size 失效。 

3)若取值为 join,则在加载 1 的那一端的对象时, 使用迫切左外连接(使用左外链接进行查询, 且把集合属性进行初始化)的方式检索 n 的一端的集合属性。并且忽略 lazy 属性。

注意:HQL 查询忽略 fetch=join 这个设置。即Query 的list() 方法会忽略映射文件中配置的迫切左外连接检索策略, 而依旧采用延迟加载策略。

3、 batch-size属性

set 元素的 batch-size 属性: 设定一次初始化 set 集合的数量,可以减少sql语句。

一对多(多对多)的检索策略的配置方法

<set name="orders" table="ORDERS"  lazy="true"  batch-size="2"  fetch="subselect">

三、多对一(一对一)检索策略(<many-to-one> 标签中设置)

<many-to-one> 元素也有一个 lazy 属性和 fetch 属性。

1、lazy属性

lazy 取值为proxy 代表对应的属性采用延迟检索,默认情况是延迟检索。

lazy 取值为false
代表对应的属性采用立即检索。

2、fetch 属性

fetch默认值为 select,即通过正常的方式来初始化 set 集合元素。

fetch 取值为 join, 表示使用迫切左外连接的方式初始化 n 关联的 1 的一端的属性,且忽略 lazy 属性的设置。

迫切左外连接检索策略的优点在于比立即检索策略使用的 SELECT 语句更少。

3、batch-size属性

注意:该属性需要设置在 1 那一端的 class 元素中:

<class name="Customer" table="CUSTOMERS" lazy="true" batch-size="5">

作用: 一次性初始化 1 的这一段代理对象的个数。

4、注意:

1)Query 的 list 方法会忽略映射文件配置的迫切左外连接检索策略, 而采用延迟检索策略。

2)如果在关联级别使用了延迟加载或立即加载检索策略, 可以设定批量检索的大小, 以帮助提高延迟检索或立即检索的运行性能。

3)Hibernate 允许在应用程序中覆盖映射文件中设定的检索策略。

多对一(一对一)检索策略的配置方法:

<class name="Customer" table="CUSTOMERS"  batch-size="5">
<many-to-one name="customer" class="Customer" column="CUSTOMER_ID" lazy="false" fetch="join"></many-to-one>

四、三种检索策略的比较

1、立即检索

采用立即检索策略,会把被检索的对象,以及和这个对象关联的一对多对象都加载到缓存中。Session的get方法就使用的立即检索策略。

这种策略的优点在于,对应用程序完全透明,不管对象处于持久化状态,还是游离状态,应用程序都可以方便的从一个对象导航到与它关联的对象。

缺点就是,一方面select语句太多;另一方面可能会加载应用程序不需要访问的对象白白浪费许多内存空间。

适用场合:

1)类级别的访问

2)需要立即获取对象信息

3)采用了二级缓存





2、延迟检索

采用延迟检索策略,就不会加载关联对象的内容。直到第一次获取关联对象的相关属性时,才去加载关联对象。在不涉及关联类操作时,延迟检索策略只适用于Session的load方法。涉及关联类操作时,延迟检索策略也能够适用于get,list等操作。

在类级别操作时, 延迟检索策略,只加载类的OID不加载类的其他属性,只用当第一次访问其他属性时,才回访问数据库去加载内容。(这里使用了CGLIB生成了类的代理类)。

在关联级别操作时,延迟检索策略,只加载类本身,不加载关联类,直到第一次调用关联对象时,才去加载关联对象。

 默认模式都是用延迟加载策略。如果需要指定使用延迟加载策略。在配置文件中设置<class>的lazy=true,<set>的lazy=true或extra(增强延迟)<many-to-one>的lazy=proxy和no-proxy。

优点在于:由程序决定加载哪些类和内容,而不必全部都加载,避免了内存的大量占用和数据库的频繁访问。因此能提高检索性能,并且能节省内存空间。

缺点:在Session关闭后,就不能访问关联类对象了。 需要确保Session一直处于打开状态,调用关联对象,最后在关闭Session对象。应用程序如果希望访问游离状态代理类实例,必须保证他在持久化状态时已经被初始化。

适用场合:

1)一对多或多对多关联

2)不需要立即 访问对象信息或不需要访问。





3、左外连接检索

采用左外连接检索,能够使用Sql的外连接查询,将需要加载的关联对象加载在缓存中。<set>fetch设置为join,<many-to-one>的fetch设置为join。

优点在于:对应用程序完全透明,不管对象处于持久化状态,还是游离状态,应用程序都可以方便的从一个对象导航到与它关联的对象。使用了外连接,select语句数目少。缺点:可能会加载应用程序不需要访问的对象,白白浪费许多内存空间。复杂的数据库表连接也会影响检索性能。

适用场合:

1)多对一或多对多关联

2)需要立即访问对象信息

3)数据库具有良好的表连接性能