关于Hibernate延迟加载
延迟加载就是在当真正需要数据的时候去执行数据加载操作,从而避免一些多余的性能开销(数据库查询)。在Hibernate3中提供了对实体对象、集合和属性的延迟加载。
1.实体对象的延迟加载
延迟加载实体对象只需在实体映射关系中将lazy属性设置为true,如下:
<class name=”xxx” table=”xxx” lazy=”true”>
……
</class>
通过一个中间代理对象,Hibernate实现了实体的延迟加载,只有当用户真正发起获得实体对象属性的动作时,才真正会发起数据库查询操作。所以实体的延迟加载是用通过中间代理类完成的,所以只有session.load()方法才会利用实体延迟加载,因为只有session.load()方法才会返回实体类的代理类对象。
实体对象的延迟加载是通过代理类来实现的,当调用session.load()时,首先返回实体对象的一个代理类,当访问该实体的方法或属性时才真正执行数据加载。
2.集合类型的延迟加载
集合类型的延迟加载在实际应用中比较常见,比如在一对多关联中定义的集合属性,如下:
<class name=”net.sample.entity.Product” table=”product”>
…..
<set name="productAttributes" table="PRODUCT_ATTRIBUTE" inverse="true" lazy="true">
<cache usage=”read-only”/>
<key column=”product_id”/>
<one-to-many class="net.sample.entity.ProductAttribute"/>
</set>
</class>
通过将<set>元素的lazy属性设置为true就打开了集合类型的延迟加载特性。此时当访问实体对象时,Hibernate不会立即加载关联对象的数据集,只有当访问关联对象集合中的对象时,Hibernate才会加载相应的关联对象实体。
在Hibernate中,集合类型的缓存是分两部分进行的,首先是实体的ID列表(即数据索引),然后才是实体对象。Hibermate在加载集合类型时,先查在缓存中查找数据索引,如果没有找到对应的数据索引就会发起一条Select语句的查询,取得符合条件的数据组装成实体对象集合和数据索引,并纳入缓存中,并返回实体对象集合。如果能找到数据索引,Hibernate就会从数据索引中取到ID列表,然后根据ID在缓存中取得相应的实体,如果缓存中不存在该实体,则发起select查询。
在上面的配置中,我们采用了<cache usage=”read-only”/>配置,在这种策略下Hibernate将只会对数据索引进行缓存,而不会对集合中的实体对象进行缓存。那么在第二次再次加载关联实体时,Hibernate找到了对应实体的数据索引,但根据数据索引,却无法在缓存中找到对应的实体,此时Hibernate就会根据找到的数据索引再发起select SQL的查询操作,以取得相对应的实体对象。
如果我们需要对集合类型中的实体也进行缓存,那么应该将cache设置为<cache usage=”read-write”/> ,此时Hibernate不光缓存了数据索引,同时还缓存在集合中的实体对象。
3.属性延迟加载
这个机制主要是为提高大数据对象的查询,比如表中存在java.sql.Clob类型字段,其中存放的是一个大数据量的信息,这种大数据对象的读取本身会带来一定的性能开销,而应用中却不是时常在使用该信息。此时,我们就可以通过属性延迟加载机制,来使我们只有当真正需要操作这个字段时,才去读取这个字段的数据。
<class name=”net.sample.entity.Product” table=”product”>
…..
<property name=”productDescription” type=”java.sql.Clob” column=”product_description” lazy=”true”/>
</class>
通过对<property>元素的lazy属性设置true来开启属性的延迟加载。
我们也可以使用注解方式来实现延迟加载,如下例,示例了属性和集合延迟加载:
@Basic(fetch = FetchType.LAZY)
@Column(name = " product_description", columnDefinition = "")
protected byte[]productDescription;
@OneToMany(fetch = FetchType.LAZY)
@Column(name = " PRODUCT_ATTRIBUTE ")
public Set getCategory() {
return this. productAttributes;
}
延迟加载虽然可以带来更好的性能,但这种技术的有一个缺点:就它要求 Hibernate Session要在对象使用的时候保持打开状态。所以在Spring+Hibernate的Web 应用中使用延迟加载时经常会遇上LazyInitializationException异常。
Spring 提供了 OpenSessionInViewFilter 和 OpenSessionInViewInterceptor 来解决Web表现层延迟加载的情况。这两个类实现了相同的功能,不同的是OpenSessionInViewInterceptor在 Spring 容器中运行并被配置在 web 应用的上下文中,而OpenSessionInViewFilter在 Spring 之前运行并被配置在 web.xml 中。这两个类都实现了在一个页面请求时打开 Hibernate的Session,并一直保持这个Session,直到这个请求结束。请求结束时, Hibernate 会话就会在 Filter 的 doFilter 方法或者 Interceptor 的 postHandle 方法中被关闭。
Interceptor的配置:
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="openSessionInViewInterceptor"/>
</list>
</property>
<property name="mappings">
</bean>
<bean name="openSessionInViewInterceptor" class="org.springframework.orm.hibernate.support.OpenSessionInViewInterceptor">
<property name="sessionFactory"><ref bean="sessionFactory"/></property>
</bean>
Filter的配置
<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate.support.OpenSessionInViewFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>hibernateFilter</filter-name>
<url-pattern>*.c</url-pattern>
</filter-mapping>
Spring框架中还提供了通过AOP拦截器HibernateInterceptor方式来支持延迟加载。这个拦截器在进入方法调用之前为当前线程绑定一个新的Hibernate Session实例,在方法执行完毕之后关闭并移除。所以拦截器可以拦截业务对象的调用,在调用前打开hibernate session,在调用结束时关闭这个session。见下例子:
public interface BaseService{
……
}
public class BusinessService implements BaseService {
public void invoke() {
// call the business logic
// Access the DAO to get the data Objects
// data objects lazily
}
}
在Spring上下文配置中,我们可以通过HibernateInterceptor拦截对BusinessService的调用来支持延迟访问数据对象。如下:
<bean id="hibernateInterceptor" class=" org.springframework.orm.hibernate3.HibernateInterceptor">
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
</bean>
<bean id=" businessSerivceTarget" class="org.sample.BusinessService">
<property name="baseDAO"><ref bean="baseDAO"/></property>
</bean>
<bean id="businessSerivce" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target"><ref bean="businessSerivceTarget"/></property>
<property name="proxyInterfaces">
<value>org.sample.BaseService</value>
</property>
<property name="interceptorNames">
<list>
<value>hibernateInterceptor</value>
</list>
</property>
</bean>
</beans>
当businessSerivce的实例被引用时,HibernateInterceptor就会打开一个hibernate session。当BusinessService执行完成后,HibernateInterceptor将关闭这个session。调用者不需要知道持久层的细节就可以使用延迟加载访问数据对象。
参考:Hibernate和Spring的延迟加载和DAO模式