Hibernate 延迟加载

Hibernate 延迟加载

主题

概念

集合属性的延迟加载

关联实体的延迟加载

延迟加载的初始化错误

参考资料

 

概念

延迟加载是Hibernate为提高程序执行效率而提供的一种机制,即只有真正需要数据的时候,才真正执行数据加载操作。

Hibernate中默认采用延迟加载的情况主要有以下几种

1.当调用session的load()方法加载一个实体时,会采用延迟加载;

2.实体的集合属性默认延迟加载;

3.实体的关联实体默认延迟加载;

延迟加载确实会给程序的查询效率带来好处,但有时明确知道数据需要立即加载,如果Hibernate先默认使用延迟加载,而后又必须去数据库加载,反而会降低效率。

 

集合属性的延迟加载

当Hibernate从数据库中初始化某个持久化实体时,该实体的集合属性是否随持久化类一起初始化呢?

如果集合属性里包含十万,数百万的记录,在初始化持久化类的同时获取集合属性的值,将导致性能的下降。可以不必要一次性加载所有的属性,等系统需要集合属性时才从数据库加载关联的数据。

此处有Person类和Address类,是一对多的关系;Person有集合属性,是Address的集合。

Hibernate对集合属性采取默认延迟加载,即 lazy=true;若访问某ID的Person实体,Hibernate只会加载Person实体除集合属性外的其他属性。这种做法能减少与数据库的交互,避免里加载Address实体集合带来的内存开销。

 

 

加载Person实体时,Hibernate没有从Address表中加载数据,这就是延迟加载。

那么Hibernate在加载Person实体时,集合属性值是什么?从Eclipse的Variables窗口可以看到

NewImage

由上图可知,集合属性addresses不是HashSet,HashMap等集合的实现类,而是PersistentSet实现类,这是Hibernate为Set接口提供的一个实现类。

PersistentSet没有加载Address数据库表的数据,里面有个session属性,这个session就是Hibernate的Session,当系统需要访问集合属性时,底层会通过PersistentSet里的session属性去加载与集合属性关联的数据库记录。

由上所知,Hibernate对于Set属性延迟加载的关键是PersistentSet实现类。在延迟加载时,PersistentSet不包含任何记录,但是有一个Hibernate Session属性,当系统需要加载集合记录时,可通过该session属性从数据库加载关联的记录。

除PersistentSet外,Hibernate 还提供了PersistentList,PersistentMap,PersistentSortedMap,PersistentSortedSet等实现类。

Hibernate要求声明集合只能用Set,List,Map,SortedSet,SortedMap等接口,不能用HashSet,HashMap等接口的实现类,原因就是Hibernate需要对属性集合进行延迟加载,而Hibernate需要通过PersistentSet等Hibernate自己实现的接口实现类来完成延迟加载,因此Hibernate要求必须用集合接口,而非集合接口实现类来声明集合属性。

 

关联实体的延迟加载

默认情况下,Hibernate也会采用延迟加载来加载关联实体。

这里讨论的关联实体是单个实体,非集合形式的实体。

关联单个实体时也是通过 lazy=true 来指定延迟加载。

此处通过Address类来讨论关联实体的延迟加载,Address有一个Person类型的实体属性。在加载Address类的时候,从Eclipse的Variables窗口可看到

NewImage

由上图可知,Address所关联的Person实体不是Person对象,而是一个Person_$$_javassist_0 类的实例。这个类是Hibernate使用Javassist项目生成的代理类。当Hibernate延迟加载关联实体时,会采用Javassist生成动态代理类,这个代理对象负责代理暂未加载的关联实体。

如果系统需要加载关联实体,关联实体的代理对象会负责去加载真正的关联实体,并返回实际的关联实体。

Hibernate采用延迟加载管理关联实体,在加载实体时,并没有获取该实体的关联实体对应的数据,只是动态生成一个关联实体的代理对象。当系统需要使用关联实体时,代理对象会负责从数据库获取记录,并初始化真正的关联实体。

代理对象具有查询数据库的能力。Hibernate通过CGLIB,来实现动态构造一个目标对象的代理对象,并且在代理对象中包含目标对象的所有方法和属性,而且所有属性被赋值为null,真正的目标对象包含在代理对象的target属性中。如果调用目标对象的方法获取属性值,通过CGLIB赋予的回调机制,实际调用的是代理对象的同名方法。当调用该方法时,Hibernate首先会检查target属性是否为null,如果不为null,则调用目标对象的对应方法。如果为null,则会真正发起数据库查询,生成SQL语句查询数据来构造目标对象,并将它赋值到target属性中。但是如果系统访问的是getId()方法,Hibernate不会初始化代理对象,因为在创建代理对象的时候ID就已经存在了,不必再去数据库中查询。

在Hibernate的延迟加载中,客户的程序开始获取的只是一个动态生成的代理对象,而真正的实体对象则委托给代理对象来管理-这就是典型的代理模式。

 

延迟加载的初始化错误

如果对一个集合属性或者关联实体配置了延迟加载,那么代理对象或者代理集合处于持久化状态,即处于session范围内时,才能初始化它。如果初始化时session已经关闭,就会出现初始化错误。

Spring框架为Hibernate延迟加载和DAO模式的整合提供了一种方便的解决方法。Spring提供了OpenSessionInViewFilter和OpenSessionInViewInterceptor。这两种方法实现的功能相同。不同点在于拦截器Interceptor在Spring容器中运行并被配置在web应用上下文中,过滤器Filter在Spring之前运行并配置在web.xml中。

OpenSessionInViewFilter是一个过滤器,用来把一个Hibernate Session和一次完整的请求过程对应的线程绑定。目的是为了实现“Open Session  in View”的模式。

 

参考资料

http://developer.51cto.com/art/200909/154624.htm

http://www.ibm.com/developerworks/cn/java/j-lo-hibernatelazy/

http://blog.sina.com.cn/s/blog_451f596201014zjp.html

 

 

 

        

 

 

 

posted @ 2014-03-19 15:14  游园惊梦  阅读(401)  评论(0编辑  收藏  举报