Hibernate之lazy延迟加载

延迟加载

延迟加载(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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值