Hibernate之lazy延迟加载
延迟加载
概念:
延迟加载:在真正使用数据的时候才发起查询,不用的时候不查询关联的数据,延迟加载又叫按需查询(懒加载)
立即加载:不管用不用,只要一调用方法,马上发起查询。
使用场景:在对应的四种表关系中,一对多、多对多通常情况下采用延迟加载,多对一、一对一通常情况下采用立即加载。
什么时候用懒加载呢,我只能回答要用懒加载的时候就用懒加载。
至于为什么要用懒加载呢,就是当我们要访问的数据量过大时,明显用缓存不太合适,
因为内存容量有限 ,为了减少并发量,减少系统资源的消耗,
我们让数据在需要的时候才进行加载,这时我们就用到了懒加载
lazy只对session.Load和获取级联信息get方法(1对1,1对多,多对多)时起作用;
延迟加载(lazy load懒加载)是在真正需要数据时才执行SQL语句进行查询,避免了无谓的性能开销
延迟加载策略的设置分为
类级别的查询策略
一对多和多对多关联的查询策略
多对一关联的查询策略
级别 Lazy属性取值
(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`"/>
如果程序加载一个持久化的目的是访问它的属性,则可以采用立即加载。如果程序加载一个持久化对象的目的仅仅是获得它的引用,则可以采用延迟加载。
1 Transaction tx = null; 2 session = HibernateUtil.currentSession(); 3 tx = session.beginTransaction(); 4 Dept dept = (Dept) session.load(Dept.class, new Byte("10")); 5 Emp emp = new Emp(); 6 emp.setEmpName("Tom"); 7 emp.setDept(dept); 8 session.save(emp); 9 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。
原文链接:https://blog.csdn.net/weixin_40826349/article/details/83722255

浙公网安备 33010602011771号