延迟加载策略简介
Hibernate 的延迟加载(lazy load)是一个被广泛使用的技术。这种延迟加载保证了应用只有在需要时才去数据库中抓取相应的记录。通过延迟加载技术可以避免过多、过早地加载数据表里的数据,从而降低应用的内存开销。Hibernate 的延迟加载本质上就是代理模式的应用,当程序通过 Hibernate 装载一个实体时,默认情况下,Hibernate 并不会立即抓取它的集合属性、关联实体所以对应的记录,而是通过生成一个代理来表示这些集合属性、关联实体,这就是代理模式应用带来的优势。
但是,延迟加载也是项目开发中特别常见的一个错误。如果对一个类或者集合配置了延迟检索策略,那么必须当代理类实例或代理集合处于持久化状态(即处于Session范围内)时,才能初始化它。如果在游离状态时才初始化它,就会产生延迟初始化错误。所以,在开发独立的DAO数据访问层时应该格外小心这个问题。
如果在获取对象的时候使用的是session.get()是不会延迟加载的,只有在使用load、hql时候才会延迟加载。
session.get()方法:
无论是否在class级别上设置了延迟加载,在调用get方法时,Hibernate都会直接去查询数据库,并加载所有的普通属性,然后返回一个真正的person对象。
对于关联对象和集合属性,Hibernate会根据为关联对象和集合属性设置的加载策略,来决定是否延迟加载。
session.load()方法:
如果在类的级别上设置了延迟加载,在使用load方法加载对象时,Hibernate并不会去查询数据库,而是直接返回一个代理对象,这个代理对象只有id属性有值,其余的属性全部为空。当第一次使用代理对象的普通属性时,Hibernate会从数据库中查询出所有的普通属性(无论普通属性的加载策略是什么);同时根据集合属性、关联对象的加载策略来决定是否加载集合属性、关联对象。从下面的代码可以产出这一点:
下面是Person.hbm.xml的部分内容:
<!--在class的级别上设置了延迟加载,这会使hibernate在load对象时不会去查询数据库,而是返回一个代理对象--> <class name="Person" table="person" lazy="true"> <id name="id" type="int" column="id"> <generator class="identity"/> </id> <!--name和age为普通属性,都设置为延迟加载--> <property name="name" type="string" column="name" lazy="true"/> <property name="age" type="integer" column="age" lazy="true"/> <!--mainAddress为管理对象,设置为延迟加载--> <many-to-one name="mainAddress" class="Address" column="address_id" unique="true" cascade="all" lazy="proxy"/> <!--addressre为集合对象,设置为延迟加载--> <set name="addresses" inverse="true" cascade="all" lazy="true"> <key column="person_id" unique="false"/> <one-to-many class="Address"/> </set> </class>
下面是查询person对象的代码:
Session session = HibernateUtil.openSession(); //执行下面的一行代码时,hibernate没有在控制台输出SQL语句,说明hibernate没有去查询数据库 //而是直接返回了一个代理对象,该代理对象只有id属性有值,其余的属性全部为空 Person p = (Person) session.load(Person.class, 2); //执行下面的一行代码时,也没有输出SQL语句,说明id属性的值来自代理对象 System.out.println(p.getId()); // 第一次使用普通属性时,Hibernate会把所有的普通属性全部加载进来(无论这些普通属性的加载策略是什么) // 但是Hibernate会根据关联属性和集合属性的加载策略来决定是这时(即加载普通属性时)加载,还是等到使用时再加载(即延迟加载) //执行下面的一行代码时,可以从控制台显示的SQL语句看出,hibernate把person对象的全部普通属性都查询了出来 System.out.println(p.getName()); //执行下面的一行代码时,没有输出SQL语句却可以输出age的值,这是因为hibernate在执行上一条语句时已经把age的值也查询出来了 System.out.println(p.getAge()); //执行下面的一行代码时,输出了查询mainAddress的SQL语句,这说明关联对象的加载策略生效了 System.out.println(p.getMainAddress().getCity()); //执行下面的一行代码时,输出了查询addresses的SQL语句,这说明集合属性的记载策略也生效了 for (Address a : p.getAddresses()) { System.out.println(a.getCountry() + a.getProvince() + a.getCity()); } HibernateUtil.closeSession();
下面是在控制台输出的内容:
2 select person0_.id as id1_13_0_, person0_.name as name2_13_0_, person0_.age as age3_13_0_, person0_.address_id as address_4_13_0_ from person person0_ where person0_.id=? 李刚 20 select address0_.id as id1_0_0_, address0_.country as country2_0_0_, address0_.province as province3_0_0_, address0_.city as city4_0_0_, address0_.person_id as person_i5_0_0_ from address address0_ where address0_.id=? 济南市 select addresses0_.person_id as person_i5_13_1_, addresses0_.id as id1_0_1_, addresses0_.id as id1_0_0_, addresses0_.country as country2_0_0_, addresses0_.province as province3_0_0_, addresses0_.city as city4_0_0_, addresses0_.person_id as person_i5_0_0_ from address addresses0_ where addresses0_.person_id=? 中国山东省日照市 中国山东省东营市
如果没有在class的级别上设置延迟加载,在使用load方法加载对象时,Hibernate会直接去查询数据库,返回一个真正的person对象(而不是一个代理对象),同时加载id和全部的普通属性。但是会根据关联对象和集合对象的加载策略来决定是否加载关联对象和集合对象(即如果关联对象和集合属性设置为延迟加载,那么只有在使用它们时才回去加载;否则在hibernate加载普通属性时就会预先加载它们)。从下面的代码可以看出这一点:
Person.hbm.xml
<!--在类的级别设置为非延迟加载--> <class name="Person" table="person" lazy="false"> <id name="id" type="int" column="id"> <generator class="identity"/> </id> <!--name和age属性为普通属性,虽然设置为延迟加载,但hibernate依然回预先加载--> <property name="name" type="string" column="name" lazy="true"/> <property name="age" type="integer" column="age" lazy="true"/> <!--mainAddress为关联对象,设置为延迟加载,hibernate会延迟加载它--> <many-to-one name="mainAddress" class="Address" column="address_id" unique="true" cascade="all" lazy="proxy"/> <!--addresses为集合属性,也设置为延迟加载,hibernate会延迟加载它--> <set name="addresses" inverse="true" cascade="all" lazy="true"> <key column="person_id" unique="false"/> <one-to-many class="Address"/> </set> </class>
下面是查询person对象的代码:
Session session = HibernateUtil.openSession(); //执行下面一行代码时,控制台会输出一条查询person对象的SQL语句,这就是在类的级别设置不延迟加载的效果,这条SQL语句将查询出id和全部的普通属性 Person p = (Person) session.load(Person.class, 2); //因为id、name、age已在上面一条代码执行时全部查处,所以可以直接输出 System.out.println(p.getId()); System.out.println(p.getName()); System.out.println(p.getAge()); //因为mainAddress设置了延迟加载,所以只有在使用它时才会去加载它 //控制台会输出一条查询mainAddress的SQL语句 System.out.println(p.getMainAddress().getCity()); //因为addresses设置了延迟加载,所以只有在使用它时才会去加载它 //控制台会输出一条查询addresses的SQL语句 for (Address a : p.getAddresses()) { System.out.println(a.getCountry() + a.getProvince() + a.getCity()); } HibernateUtil.closeSession();
下面是在控制台输出的内容:
select person0_.id as id1_13_0_, person0_.name as name2_13_0_, person0_.age as age3_13_0_, person0_.address_id as address_4_13_0_ from person person0_ where person0_.id=? 2 李刚 20 select address0_.id as id1_0_1_, address0_.country as country2_0_1_, address0_.province as province3_0_1_, address0_.city as city4_0_1_, address0_.person_id as person_i5_0_1_, person1_.id as id1_13_0_, person1_.name as name2_13_0_, person1_.age as age3_13_0_, person1_.address_id as address_4_13_0_ from address address0_ left outer join person person1_ on address0_.person_id=person1_.id where address0_.id=? 济南市 select addresses0_.person_id as person_i5_13_1_, addresses0_.id as id1_0_1_, addresses0_.id as id1_0_0_, addresses0_.country as country2_0_0_, addresses0_.province as province3_0_0_, addresses0_.city as city4_0_0_, addresses0_.person_id as person_i5_0_0_ from address addresses0_ where addresses0_.person_id=? 中国山东省日照市 中国山东省东营市
对象的id属性是一定预先加载的,因为后续的查询需要根据id来查询。
Hibernate中允许使用延迟加载的地方主要有以下几个地方:
<hibernate-mapping default-lazy=(true|false)”true”>:设置全局的延迟加载策略。
<class lazy=(true|false)>:DTD没设置默认值,推理默认值为true
<property lazy=(true|false)>:设置字段延迟加载,默认为false
<component lazy=(true|false):默认为false
<subclass lazy=(true|false)>:默认设置为true
<join-subclass lazy=(true|false)>:默认设置为true
<union-subclass lazy=(true|false)>:默认设置为true
<many-to-one lazy=(proxy|no-proxy|false)>:默认为proxy
<one-to-one lazy=(proxy|no-proxy|false)>:默认为proxy
<list lazy=(true|extra|false)>:默认为true
<set lazy=(true|extra|false)>:默认为true
<map lazy=(true|extra|false)>:默认为true
<bag lazy=(true|extra|false)>:默认为true
<idbag lazy=(true|extra|false)>:默认为true
1、对象的延迟加载(<class/>元素)分析 true或false
1.1 延迟加载 lazy=true
如果想对实体对象使用延迟加载,必须要在实体的映射配置文件中进行相应的配置
如果在class的级别设置了延迟加载,那么在用session.load(Person.class, 1);加载对象时,Hibernate不会去查询数据库(因为在控制台根本就不会输出SQL语句),而是直接返回了一个代理对象,这个代理对象只有id属性有值,其余的属性全部为空。只有当使用到除id以外的的属性时,hibernate才会去查询数据库。如果在使用除id以外的属性时session已经关闭,那么Hibernate就会抛出异常。
<class name= "Person" table= "PERSON" lazy= "true"> </class>
tx = session.beginTransaction(); Person p=(Person) session.load(Person.class, "001");//(1) System.out.println("0: "+p.getPersonId());//(2) System.out.println("0: "+p.getName());//(3) tx.commit(); session.close();
执行到(1)并没有出现sql语句,并没有从数据库中抓取数据。这个时候查看内存对象p如下:
观察person对象,我们可发现是Person$$EnhancerBy..的类型的对象。这里所返回的对象类型就是Person对象的代理对象,在hibernate中通过使用CGLB来先动态构造一个目标对象的代理类对象,并且在代理对象中包含目标对象的所有属性和方法。所以,对于客户端而言是否为代理类是无关紧要的,对他来说是透明的。这个对象中,仅仅设置了id属性(即personId的值),这是为了便于后面根据这个Id从数据库中来获取数据。
运行到(2)处,输出为001,但是仍然没有从数据库里面读取数据。这个时候代理类的作用就体现出来了,客户端觉得person类已经实现了(事实上并未创建)。但是,如果这个会话session关闭,再使用person对象就会出错了。
调试运行到(3)处,要用到name属性,但是这个值在数据库中。所以hibernate从数据库里面抓取了数据,sql语句如下所示:
select person0_.PERSONID as PERSONID3_0_, person0_.NAME as NAME3_0_ from PERSON person0_ where person0_.PERSONID=?
这时候,我们查看内存里面的对象如下:
真正的Person对象放在CGLIB$CALLBACK_0对象中的target属性里。
这样,通过一个中间代理对象,Hibernate实现了实体的延迟加载,只有当用户真正发起获得实体对象属性的动作时,才真正会发起数据库查询操作。所以实体的延迟加载是用通过中间代理类完成的,所以只有session.load()方法才会利用实体延迟加载,因为只有session.load()方法才会返回实体类的代理类对象。
1.2 非延迟加载策略分析 lazy=false
Hibernate默认的策略便是非延迟加载的,所以设置lazy=false。
如果没有在class的级别上设置延迟加载,在使用load方法加载对象时,Hibernate会直接去查询数据库,返回一个真正的person对象(而不是一个代理对象),同时加载id和全部的普通属性。但是会根据关联对象和集合对象的加载策略来决定是否加载关联对象和集合对象(即如果关联对象和集合属性设置为延迟加载,那么只有在使用它们时才回去加载;否则在hibernate加载普通属性时就会预先加载它们)。
tx = session.beginTransaction(); Person p=(Person) session.load(Person.class, "001");//(1) System.out.println("0: "+p.getPersonId());//(2) System.out.println("0: "+p.getName());//(3) tx.commit(); session.close();
调试运行到(1)处时,hibernate直接执行如下sql语句:
select person0_.PERSONID as PERSONID3_0_, person0_.NAME as NAME3_0_ from PERSON person0_ where person0_.PERSONID=?
我们在查看内存快照如下:
这个时候就不是一个代理类了,而是Person对象本身了。里面的属性也已经全部 普通属性也全部被加载。这里说普通属性是因为addresses这个集合对象并没有被加载,因为set自己本身也可以设置lazy属性。所以,这里也反映出class对象的lazy并不能控制关联或集合的加载策略。
1.3 总结
如果设置lazy="true"(默认为true)。在load的时候Hibernate并没有到数据库中查询,而是仅仅返回了一个代理对象,该代理对象只有id属性有值,其余的属性全部为空,当第一次使用代理对象的普通属性时,Hibernate会从数据库中查询出全部的普通属性,关联对象和集合属性会根据它自己的加载策略来决定是在这时(即加载普通属性时)加载,还是在之后的第一次使用时加载。
如果显式的设置lazy="false",load的时候即会把所有 普通属性全部读取进来(无论它们的加载策略是什么),而关联对象和 集合属性 将根据自己的加载策略决定是否延迟加载。并且,返回的将是一个真正的该类型的对象(如Person),而不是代理类。
2、属性的延迟加载(<property/>元素)分析 true或false
<property/>元素也有一个名为lazy的属性,从理论上来讲该属性可以用来控制单个属性的加载策略。但实际上,该属性并不能控制单个属性的加载策略。
属性的加载策略会受到<class/>元素加载策略的影响。
具体情况请参见 “1.3总结” 。
如果只对部分property进行延迟加载的话,hibernate提供了另外的方式,也是更为推荐的方式,即HQL或者条件查询。
3、集合属性的延迟加载(<set/>等集合元素)分析 true、false或extra
true或false:
集合元素加载策略不受<class/>元素加载策略的影响,即无论<class/>元素的加载策略是什么,集合元素均按照它自己的加载策略来决定是否延迟加载。
extra:
extra其实是一种比较智能的延迟加载,即调用集合的size/contains等方法的时候,hibernate并不会去加载整个集合的数据,而是发出一条聪明的SQL语句,以便获得需要的值,只有在真正需要用到这些集合元素对象数据的时候,才去发出查询语句加载所有对象的数据。
public String getListData() { Session session = HibernateUtil.openSession(); ListData ld = (ListData) session.load(ListData.class, 1); // HibernateUtil.closeSession(); System.out.println(ld.getId()); System.out.println("intList.length=" + ld.getIntList().size()); List<Integer> intList = ld.getIntList(); for (Integer num : intList) System.out.print(num + " "); System.out.println(); System.out.println("strList.length=" + ld.getStrList().size()); List<String> strList = ld.getStrList(); for (String str : strList) System.out.print(str + " "); System.out.println(); System.out.println("dateList.length=" + ld.getDateList().size()); List<Date> dateList = ld.getDateList(); for (Date date : dateList) System.out.print(date + " "); System.out.println(); HibernateUtil.closeSession(); return SUCCESS; }
Hibernate生成的SQL语句如下:
select listdata0_.id as id1_11_0_ from list_data listdata0_ where listdata0_.id=? select max(list_index) + 1 from int_list where foreign_id =? select intlist0_.foreign_id as foreign_1_11_0_, intlist0_.number as number2_7_0_, intlist0_.list_index as list_ind3_0_ from int_list intlist0_ where intlist0_.foreign_id=? select max(list_index) + 1 from str_list where foreign_id =? select strlist0_.foreign_id as foreign_1_11_0_, strlist0_.str as str2_16_0_, strlist0_.list_index as list_ind3_0_ from str_list strlist0_ where strlist0_.foreign_id=? select max(list_index) + 1 from date_list where foreign_id =? select datelist0_.foreign_id as foreign_1_11_0_, datelist0_.date_time as date_tim2_3_0_, datelist0_.list_index as list_ind3_0_ from date_list datelist0_ where datelist0_.foreign_id=?
总结
在集合的3中延迟加载中,我觉得最有的配置应该是extra。
Hibernate中集合属性的延迟加载应该来说是最为重要的,因为如果集合属性里面包含十万百万记录,在初始化持久实体的同时,完成所有集合属性的抓取,将导致性能急剧下降。
相关推荐
在Flex中使用Gilead和Hibernate的懒加载,首先需要确保在Hibernate配置文件中对关联实体设置了懒加载属性。例如,如果有一个`User`类和一个`Address`类,`User`中包含一个`List<Address>`,那么在`User`的映射文件中...
在探讨Hibernate框架中的`lazy`属性时,我们深入解析了其功能、应用场景以及与之相关的潜在问题,尤其关注于如何有效利用此特性以优化数据库性能和应用响应速度。 ### Hibernate框架简介 Hibernate是一个开放源码...
**标题**: Hibernate懒加载(Lazy Loading) 在Java的持久化框架Hibernate中,懒加载(Lazy Loading)是一种重要的优化策略,它的核心思想是“延迟加载”或“按需加载”。默认情况下,当一个实体被加载时,并不会...
标题中的“Hibernate lazy加载FOR Connection”指的是Hibernate框架中的一种特性,即懒加载(Lazy Loading)。在Hibernate中,懒加载是一种优化策略,它推迟对关联对象的加载,直到真正需要使用这些对象时才进行加载...
在 Hibernate 框架中,延迟加载(Lazy Loading)是一种优化数据访问性能的重要技术。它允许我们只在真正需要数据时才从数据库加载,避免一次性加载大量数据导致的内存消耗和性能瓶颈。当我们处理与实体相关的集合...
标题中的"Dwr+Hibernate的Lazy问题"涉及到两个主要技术:Direct Web Remoting (DWR) 和 Hibernate。DWR 是一种让 JavaScript 在浏览器中直接调用服务器端 Java 方法的技术,而 Hibernate 是一个流行的Java持久化框架...
在这个场景中,我们关注的是"hibernate的lazy策略forClass",这涉及到如何对类的属性进行懒加载配置。 首先,我们需要理解Hibernate中的实体类(Entity)。在Hibernate中,一个Java类可以被映射为数据库中的一个表...
当我们将一个类或集合的`lazy`属性设置为`true`时,Hibernate会在需要时才去加载关联的对象,而不是在加载主对象时一并加载。这样可以避免不必要的数据库查询,提高性能。 **3. 错误和注意事项** 延迟加载可能导致...
当我们设置Hibernate中的实体属性`lazy=true`时,这是懒加载(Lazy Loading)机制的体现。懒加载是一种优化策略,它允许我们在需要时才加载关联的对象,而不是在初始加载实体时就加载所有关联数据,从而提高性能。 ...
2. `default-lazy`:默认情况下,未显式指定`lazy`属性的Java属性和集合将被懒加载,即在需要时才加载。如果设置为`false`,则这些属性和集合将在加载实体时立即加载。 3. `auto-import`:默认为`true`,允许在HQL...
根据提供的文件信息,我们可以深入探讨Hibernate框架中的几个关键概念,特别是`fetch`, `lazy`, `cascade`, 和 `inverse`关键字的使用与理解。这四个概念在处理对象关系映射(ORM)时非常重要,尤其是在Java环境下...
在单端关联上应用懒加载,意味着只有当程序尝试访问被标记为lazy的关联属性时,Hibernate才会执行额外的SQL查询来获取关联的数据。这与Eager Loading(即时加载)相反,Eager Loading会在加载主实体时一起加载关联...
例如,在映射文件`Person.hbm.xml`中,通过`<set>`元素中的`lazy="true"`属性来声明`Person`实体的`addresses`属性应该被延迟加载: ```xml <set name="addresses" table="address" lazy="true"> ``` #### 四...
12. **延迟加载(Lazy Loading)**:为了提高性能,Hibernate支持属性和关联的延迟加载,即只有在真正需要时才加载数据。 13. **事件监听器**:Hibernate允许定义事件监听器来处理特定的数据库操作,如对象的保存、...
3. **实体类与表映射**:Hibernate使用注解或XML文件(hbm.xml)将Java类映射到数据库表,如@Table、@Column等注解,定义了类与表、属性与列的关系。 4. **Session接口**:在Hibernate中,Session是与数据库交互的...
9. **懒加载(Lazy Loading)**:Hibernate支持延迟加载,即关联的对象在需要时才从数据库加载,有效提高了程序性能。 10. **缓存机制**:Hibernate提供了第一级缓存(Session级别的)和第二级缓存(SessionFactory...
* 属性映射:将 Java 对象的属性映射到关系数据库表的列中。 * 关联映射:将 Java 对象之间的关联映射到关系数据库表之间的关联中。 六、 Hibernate 的优化 Hibernate 的优化主要包括以下几个方面: * 缓存:使用...
- `dialect` 属性定义了方言,这里是 `org.hibernate.dialect.SQLServerDialect`,用于处理 SQL Server 特有的语法。 - `show_sql` 设置为 `true`,会在控制台打印出执行的 SQL 语句,便于调试。 - `mapping` ...
1. 原理:当实体类的一个属性被声明为懒加载时,Hibernate不会在查询主对象时立即加载该属性,而是在需要时才通过单独的SQL查询获取。这样可以避免一次性加载大量数据,降低内存压力。 2. 实现:Hibernate使用代理...