延迟加载
延迟加载(lazy load懒加载)是在真正需要数据时才执行SQL语句进行查询,避免了无谓的性能开销
延迟加载策略的设置分为
类级别的查询策略
一对多和多对多关联的查询策略
多对一关联的查询策略
级别 | Lazy属性取值 |
---|---|
类级别 | <class>元素中lazy属性的可选值为true(延迟加载)和false(立即加载)。默认值为true |
一对多关联级别 | <set>元素中lazy属性的可选值为true(延迟加载)、extra(增强延迟加载)和false(立即加载)。默认值为true |
多对一关联级别 | <many-to-one>元素中lazy属性的可选值为proxy(延迟加载)、no-proxy(无代理延迟加载)和false(立即加载)。默认值为true |
(1)类级别的查询策略
lazy属性的可选值为true(延迟加载)和false(立即加载)。默认值为true
1.立即加载
在Dept.hbm.xml文件中,以下方式表示采用立即加载策略
<class name="cn.hibernatedemo.entity.Dept" lazy="false" table="`DEPT`">
通过Session的load()方式加载Dept对象时:
Dept dept= (Dept)session.load(Dept.class,new Byte("10"));
Hibernate会立即执行查询DEPT表的select语句
select * from dept where deptno=?;
2.延迟加载
类级别的默认加载策略是延迟加载。在Dept.hbm.xml文件中,以下两种方式都表示采用延迟加载策略:
<class name="cn.hibernatedemo.entity.Dept" table="`DEPT`"/>
或者
<class name="cn.hibernatedemo.entity.Dept" lazy="true" table="`DEPT`"/>
如果程序加载一个持久化的目的是访问它的属性,则可以采用立即加载。如果程序加载一个持久化对象的目的仅仅是获得它的引用,则可以采用延迟加载。
Transaction tx = null;
session = HibernateUtil.currentSession();
tx = session.beginTransaction();
Dept dept = (Dept) session.load(Dept.class, new Byte("10"));
Emp emp = new Emp();
emp.setEmpName("Tom");
emp.setDept(dept);
session.save(emp);
tx.commit();
/**因为Dept类级采用延迟加载,session.load()方法不会执行DEPT表的select语句,只返回一个Dept代理类的实例,它的deptNo属性为10,其余属性都为null。以上代码仅由session.save()方法执行一条insert语句**/
insert into emp(id,ename,...,deptno) values(1,'Tom',..,10)
当class元素的lazy属性为true时,会影响Session的load()方法的各种运行时行为,下面举例说明。
(1)通过load()方法加载的延迟状态的Dept代理实例,除了OID,其他属性均为null。通过调用其getDeptName()等方法可以促使Hibernate执行查询,获得数据从而完成代理实例的初始化。
(2)调用代理实例的getDeptNo()方法访问OID属性,不会触发Hibernate初始化代理类实例的行为,该方法会直接返回Dept代理实例的OID值,无须查询数据库。
(3)org.hibernate.Hibernate类的initialize()静态方法用于显式初始化代理类实例,isInitalized()方法用于判断代理类实例是否已经被初始化。
以下代码通过Hibernate类的initialize()方法显式初始化了Dept代理类实例。
tx = session.beginTransaction();
Dept dept = (Dept) session.load(Dept.class, new Byte("10"));
if(!Hibernate.isInitalized(dept)){
Hibernate.initialize(dept);
}
tx.commit();
(4)如果加载的Dept代理实例的OID在数据库中不存在,Session的load()方法不会立即抛出异常,因为此时并未真正执行语句。只有当Hibernate试图完成对Dept代理实例的初始化时,才会真正执行查询语句,这时会抛出以下异常:
org.hibernate.ObjectNotFoundException:No row with the given identifier exists
(5)Dept代理类的实例只有在当前Session范围内才能被初始化。如果在当前Session的生命周期内,应用程序没有完成Dept代理实例的初始化工作,那么在当前Session关闭后,试图访问该Dept代理实例中OID以外的属性(如调用getDeptName()方法),将抛出异常。
(2)一对多和多对多关联的查询策略
<set>元素中lazy属性的可选值为true(延迟加载)、extra(增强延迟加载)和false(立即加载)。默认值为true
1.立即加载
以下代码表明Dept类的emps集合采用立即加载策略
<set name="emps" inverse="true" lazy="false">....</set>
tx = session.beginTransaction();
Dept dept = (Dept) session.get(Dept.class, new Byte("10"));
tx.commit();
执行Session的get()方法时,对于Dept对象采用类级别的立即加载策略;对于Dept对象的emps集合(即与Dept关联的所有Emp对象),采用一对多关联级别的立即加载策略。因此Hibernate执行以下select语句:
select * from dept where deptno=?
select * from emp where DEPTNO=?
通过以上select语句,Hibernate加载了一个Dept对象和多个Emp对象。但在很多情况下,应用程序并不需要访问这些Emp对象,所以在一对多关联级别中不能随意使用立即加载策略。
2.延迟加载
对于<set>元素,应该优先考虑使用默认的延迟加载策略:
<set name="emps" inverse="true"></set>
或者
<set name="emps" inverse="true" lazy="true"></set>
运行,执行以下SQL语句:
select * from dept where deptno=?
Session的get()方法返回的Dept 对象中,emps属性引用一个没有被初始化的集合代理类实例.换句话说,此时emps集合中没有存放任何Emp对象,只有当emps集合代理类实例被初始化时,才会到数据库中查询所有与Dept 关联的所有Emp对象,执行以下select 语句:
select*from emp where DEPTNO=?
那么,Dept 对象的emps属性引用的集合代理类实例何时初始化呢? 主要包括以下两种情况:
(1)会话关闭前.应用程序第一次访问它时,如调用它的 iterator().size(),isEmpty()或contains()方法:
Set<Emp>emps=dept.getEmps();
Iterator<Emp> empIterator=emps.iterator();//导致emps集合代理类实例被初始化
(2)会话关闭前,通过 org.hibernate.Hibernate类的 initialize()静态方法初始化:
Set<Emp>emps=dept.getEmps();
Hibernate.initialize(emps);//导致emps集合代理类实例被初始化
3.增强延迟加载 在<set>元素中配置lazy属性为extra",表明采用增强延迟加载策略:
<set name="emps" inverse="true" lazy="extra">...</set>
增强延迟加载策略与一般的延迟加载策略(lazy="true")的主要区别在于,增强延迟加载策略能进一步延迟 Dept 对象的 emps 集合代理类实例的初始化时机。当程序第一次访问 emps 属性的iterator()方法时,会导致emps集合代理类实例的初始化,而当程序第一次访问emps属性的size()contains()和isEmpty()方法时,Hibernate不会初始化emps集合代理实例,仅通过特定的select 语句查询必要的信息,以下程序代码演示了采用增强延迟加载策略时的Hibernate 运行时行为:
tx= session.beginTransaction();
Dept dept-(Dept)session.get(Dept.class,new Byte("10"));
//以下语句不会初始化emps集合代理类实例
执行 SOL语句:select count(empno)from emp where DEPTNO-?
dept.getEmps().size();
//以下语句会初始化emps集合代理类实例
d.UIT 80L语句:select*from emp where DEPTNO-?
dept.getemps().iterator0);
tx.commit();
(3)多对一关联的查询策略
<many-to-one>元素中lazy属性的可选值为proxy(延迟加载)、no-proxy(无代理延迟加载)和false(立即加载)。默认值为true
在映射文件中,<many-to-one>元素用来设置多对一关联关系,在Emp.hbm.xml文件中,以下代码设置Emp 类与Dept 类的多对一关联关系。
<many-to-one
name-"dept"
column-DEPTNO"
lazy="proxy"
class="cn.hibernatedemo.entity.Dept"/>
1.延迟加载 在<many-to-one>元素中配置lazy属性为proxy,延迟加载与Emp 关联的Dept 对象
tx=session.beginTransaction();
rmp emp=(Emp)session.get(Emp.class,7839);
//emp.getDept)回 Dept代理类实例的引用
Dept dept =emp.getDept();
dept.getDeptName();
tx.commit();
当运行 Session的 get()方法时,仅立即执行查询Emp 对象的select 语句:
select *from emp where empno=?
Emp 对象的dept 属性引用Dept 代理类实例,这个代理类实例的OID由EMP 表的DEPTNO 外键值决定,当执行 dept.getDeptName()方法时,Hibernate 初始化Dept 代理类实例,执行以下 select 语司从数据库中加载Dept 对象:
select*from dept where deptno=?
2.无代理延迟加载 在<many-to-one>元素中配置lazy属性为"no-proxy对于以下程序代码:
tx=session.beginTransaction();
Emp emp=(Emp)session.get(Emp.class,7839);//第1行
Dept dept=emp.getDept();//第2行
dept.getDeptName();//第3行
tx.commit();
如果对Emp对象的dept属性使用无代理延迟加载,即<many-to-one>元素的 lazy 属性为no-proxy,那么程序第1行加载的Emp 对象的dept 属性为null,当程序第2行调用 emp.getDept()方法时,将触发 Hibernate执行查询DEPT表的select 语句,从而加载Dept 对象.
由此可见,当lazy 属性为proxy时,可以延长延迟加载Dept 对象的时间.而当lazy属性为no-proxy 时,则可以避免使用由Hibernate 提供的Dept 代理类实例,使Hibernate 对程序提供更加透明的持久化服务.
3.立即加载
以下代码把Emp.hbm.xml文件中<many-to-one>元素的lazy 属性设置为false:
<many-to-one
name-"dept"
column="`DEPTNO`"
lazy="false"
class="cn.hibernatedemo.entity.Dept"/>
对于以下程序代码:
tx= session.beginTransaction();
Emp emp=(Emp)session.get(Emp.class,7839);
tx.commit();
在运行session.get()方法时,Hibernate 执行以下 select语句:
select *from emp where empno=?
select *from dept where deptno=?
(4)Open Session In View模式
在Java Web 应用中,通常需要调用Hibernate API获取到要显示的某个对象并传给相应的视图 JSP.并在JSP 中根据需要通过这个对象导航到与之关联的对象或集合数据。这些关联对象或集合数 据如果是被延迟加载的,且在执行完查询后Session对象已经关闭,Hibernate 就会抛出 LazyInitializationException针对这一问题,Hibernate社区提出了 Open Session In View 模式作为解沃 方案。这个模式的主要思想是:在用户的每次请求过程中,始终保持一个Session 对象处于开启状态.
Open Session In View模式的具体实现有以下三个步骤:
第一步:把Session 绑定到当前线程上,要保证在一次请求中只有一个Session对象,Dao层BHibernateUtil.currentSession()方法使用 SessionFactory的getCurrentSession()方法莊得 Session,可 保证每次请求的处理线程上只有一个Session对象存在。
第二步:用Filter 过滤器在请求到达时打开Session,在页面生成完毕时关闭 Session
OpenSessionlnViewFilter.java的主要代码
public class OpensessionInviewFilter implements Filter
@Override
public void dorilter(ServletRequest arg0,ServletResponse arg1,
FilterChain arg2)throws IOException,ServletException{
Transaction tx=null;
try{
//请求到达时,打开Session并启动事务
tx=HibernateUtil.currentSession(),beginTransaction();
//执行请求处理链
arg2.doFilter(arg0,arg1);
//返回响应时,提交事务
tx.commit();
} catch(HibernateException e){
e.printStackTrace();
if(tx!=null)
tx.rollback();//回滚事务
}
}
//省略其他代码
}
web.xml中Filter的配置:
<filter>
<filter-name>openSessionInView</filter-name>
<filter-class>cn.hibernatedemo.web.OpenSessionInViewFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>openSessionInView</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
每次请求都在OpenSessionlnViewFilter中Session,开启事务:页面生成完毕之后在OpenSessionInViewFilter过滤器中结束事务并关闭 Session
第三步:调整业务层代码,删除和会话及事务官理相关的代码,仅保留业务逻辑代码
DeptBizlmpl.java 的主要代码:
public class DeptBizImpI implements DeptBiz{
private DeptDao deptDao= new DeptDaoImpl();
@Override
public Dept findDeptByDeptNo(Byte deptNo)throws HibernateException{
return this.deptDao.findById(deptNo);
}
}
数据访问层代码风格不变.
在 OpenSessionInViewFilter过滤器中获取 Session对象,保证了一次请求过程中始终使用一个Session对象.视图JSP从一个对象导航到与之关联的对象或集合,即使这些对象或集合是被延迟加载的,因为当前Session对象没有关闭,所以能顺利地获取到关联对象或集合的数据。直到视图JSP数据全部响应完成.OpenSessionlnViewFilter过滤器才结束事务并关闭 Session。