懒加载可以提高性能吗?
不可以简单的说"能",因为Hibernate的关系映射拖累了SQL的性能,所以想出懒加载来弥补.只是弥补而以,不会超越.所以大家不要想着使用了懒加载总体性能就提高了,其实总体性能不下降就万幸了.
懒加载为Hibernate中比较常用的特性之一,下面我们详细来了解下懒加载的原理和注意事项
Load()方法的懒加载原理
在Hibernate中,查询方法有两个,分别是get()和load(),这两种方法的不同就是load()拥有懒加载的特性。Load()方法就是在查询某一条数据的时候并不会直接将这条数据以指定对象的形式来返回,而是在你真正需要使用该对象里面的一些属性的时候才会去数据库访问并得到数据。他的好处就是可以减少程序本身因为与数据库频繁的交互造成的处理速度缓慢。
以一个Person类做例子,我们写一个查询的方法如下:
1 public static void query(int id){ 2 Session session=null; 3 try{ 4 session=HibernateUtil.getSession(); 5 Person person=(Person) session.load(Person.class, id); 6 //System.out.println(person.getName()); 7 }catch(HibernateExceptionex){ 8 ex.printStackTrace(); 9 }finally{ 10 if(session!=null){ 11 session.close(); 12 } 13 } 14 }
然后在Hibernate配置文件中添加以下属性
<property name="hibernate.show_sql">true</property>
这条属性的作用为将Hibernate运行时产生的每一条SQL语句都打印出来
运行上述方法后,我们并没有看到Hibernate打印任何查询语句,这时我们可以将注释的语句重新调回来,让他打印查询到的person的name。这时我们可以看到Hibernate产生的查询语句并看到person的name属性。这就是懒加载了。
那么在Hibernate懒加载的时候,返回的对象是空的吗?答案是否定的,我们可以通过打印person.getClass()方法来验证,打印出来的结果并不是null,而是一个Person后面加了一堆很奇怪的字符的类。可以肯定的是懒加载的对象并不是空,而且这个对象的类型不是Person类。那么他到底是什么呢?
其实这个兑现我们管它叫做代理对象,而这个对象所属的类是Person类的子类,是Hibernate自动实现的一个子类。这个子类的特点是:当你访问person对象的某一个属性的时候,他会自动查询数据库中对应这个对象的数据并返回,这就是为什么在创建对象关系映射的时候要求实体类不能够为final类型的原因了。
但是这个对象是有一个生命周期的,我们可以改写上述方法如下:
1 public static Person query(int id){ 2 Session session=null; 3 Person person=null; 4 try{ 5 session=HibernateUtil.getSession(); 6 person=(Person)session.load(Person.class, id); 7 //System.out.println(person.getName()); 8 }catch(HibernateExceptionex){ 9 ex.printStackTrace(); 10 }finally{ 11 if(session!=null){ 12 session.close(); 13 } 14 } 15 return person; 16 }
调用这个方法并返回查询到的代理对象,我们可以在返回该对象后打印该对象的name属性,这时会抛出一个org.hibernate.LazyInitializationException异常
这就是懒加载不能初始化异常,这就说明了一件事,懒加载的时候如果想通过代理对象查询数据库,需要在该session关闭以前才可以。但如果一定要在session关闭以后再使用代理对象的话,Hibernate中定义了一个初始化代理对象的方法initialize(),通过该方法即可将代理对象初始化。
注:在使用代理对象的getId()方法和getClass()方法的时候,并不会抛出不能初始化异常,因为这两个属性并不用查询数据库。
懒加载可以用于关系映射和集合属性的操作,而且懒加载是可以关闭并打开的,下面我们会根据不同的情况分析一下懒加载。
一对一的懒加载分析
一对一的懒加载并不常用,因为懒加载的目的是为了减少与数据库的交互,从而提高执行效率,而在一对一关系中,主表中的每一条数据只对应从表的一条数据库,就算都查询也不会增加多少交互的成本,而且主表不能有contrained=true,所以主表是不能懒加载的。(从表可以有)
注:当fetch设置为join时,懒加载就会失效。因为fetch的作用是抓取方式,他有两个值分别问select和join,默认值为select。即在设为join时,他会直接将从表信息以join方式查询到而不是再次使用select查询,这样导致了懒加载的失效。
一对多和多对多的懒加载分析
与一对一关联不同,一对多和一对多的关联下,主表的每一条属性都会对应从表的多条数据,这个时候懒加载就显得非常有效了。比如一个部门里面有多个员工,如果没有懒加载,每查询这个部门的时候都会查询出多个员工,这会大大增加与数据库交互的成本。所以Hbernate默认的是加入懒加载的。这就是查询集合属性的时候返回的是一个PersistentIndexed*类型对象的原因。该对象其实就是一个代理对象。当然,可以在映射文件中通过将lazy属性设为假来禁用。
多对一的懒加载分析
虽然多对一与一对一关系方式相同,但是在Hibernate中多对一时,默认是进行懒加载的。另外有一点需要注意的是懒加载并不会区分集合属性里面是否有值,即使是没有值,他依然会使用懒加载,这也是懒加载不够完善的地方之一。
懒加载的一些细节扩充
有的时候,我们在session关闭后仍需要使用懒加载的代理对象来查询数据库,这时我们就需要将代理对象初始化,不过问题是,当我们不能确定初始化后就一定使用该对象的时候怎么办,这样不是又白白浪费了资源吗?我们可以在使用代理对象的方法里面加入一个布尔值参数,这样当我们不需要初始化代理对象的时候只要将布尔参数设为假。但也是有缺点的,因为在调用的时候,尤其是在别人使用的时候,参数越多,方法学习成本就会增加,所以请酌情处理。
懒加载也可以用于某些简单属性,但是因为实现起来比较复杂,而且效果并不明显,所以并不推荐。