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窗口可以看到
由上图可知,集合属性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窗口可看到
由上图可知,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