Hibernate与延迟加载机制探究
前言
Hibernate对象关系映射提供延迟的与非延迟的对象初始化。非延迟加载在读取一个对象的时候会将与这个对象所有相关的其他对象一起读取出来。这有时会导致成百的(如果不是成千的话)select语句在读取对象的时候执行。这个问题有时出现在使用双向关系的时候,经常会导致整个数据库都在初始化的阶段被读出来了。当然,你可以不厌其烦地检查每一个对象与其他对象的关系,并把那些最昂贵的删除,但是到最后,我们可能会因此失去了本想在ORM工具中获得的便利。
一个明显的解决方法是使用Hibernate提供的延迟加载机制。这种初始化策略只在一个对象调用它的一对多或多对多关系时才将关系对象读取出来。这个过程对开发者来说是透明的,而且只进行了很少的数据库操作请求,因此会得到比较明显的性能提升。这项技术的一个缺陷是延迟加载技术要求一个Hibernate会话要在对象使用的时候一直开着。这会成为通过使用DAO模式将持久层抽象出来时的一个主要问题。为了将持久化机制完全地抽象出来,所有的数据库逻辑,包括打开或关闭会话,都不能在应用层出现。最常见的是,一些实现了简单接口的DAO实现类将数据库逻辑完全封装起来了。一种快速但是笨拙的解决方法是放弃DAO模式,将数据库连接逻辑加到应用层中来。这可能对一些小的应用程序有效,但是在大的系统中,这是一个严重的设计缺陷,妨碍了系统的可扩展性。
Web层进行延迟加载
幸运的是,Spring框架为Hibernate延迟加载与DAO模式的整合提供了一种方便的解决方法。对那些不熟悉Spring与Hibernate集成使用的人,我不会在这里讨论过多的细节,但是我建议你去了解Hibernate与Spring集成的数据访问。以一个Web应用为例,Spring提供了OpenSessionInViewFilter和OpenSessionInViewInterceptor。我们可以随意选择一个类来实现相同的功能。两种方法唯一的不同就在于interceptor在Spring容器中运行并被配置在web应用的上下文中,而Filter在Spring之前运行并被配置在web.xml中。不管用哪个,他们都在请求将当前会话与当前(数据库)线程绑定时打开Hibernate会话。一旦已绑定到线程,这个打开了的Hibernate会话可以在DAO实现类中透明地使用。这个会话会为延迟加载数据库中值对象的视图保持打开状态。一旦这个逻辑视图完成了,Hibernate会话会在Filter的doFilter方法或者Interceptor的postHandle方法中被关闭。下面是每个组件的配置示例:
Interceptor的配置
-
<beans>
-
<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>
-
</beans>
Filter的配置
-
<web-app>
-
<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>*. spring </url-pattern>
-
</filter-mapping>
-
</web-app>
实现Hibernate的Dao接口来使用打开的会话是很容易的。事实上,如果你已经使用了Spring框架来实现你的Hibernate Dao,很可能你不需要改变任何东西。方便的HibernateTemplate公用组件使访问数据库变成小菜一碟,而DAO接口只有通过这个组件才可以访问到数据库。下面是一个示例的DAO:
-
public class HibernateProductDAO extends HibernateDaoSupport implements ProductDAO {
-
public Product getProduct(Integer productId) {
-
return (Product)getHibernateTemplate().load(Product.class, productId);
-
}
-
public Integer saveProduct(Product product) {
-
return (Integer) getHibernateTemplate().save(product);
-
}
-
public void updateProduct(Product product) {
-
getHibernateTemplate().update(product);
-
}
-
}
业务逻辑层中使用延迟加载
即使在视图外面,Spring框架也通过使用AOP 拦截器 HibernateInterceptor来使得延迟加载变得很容易实现。这个Hibernate 拦截器透明地将调用配置在Spring应用程序上下文中的业务对象中方法的请求拦截下来,在调用方法之前打开一个Hibernate会话,然后在方法执行完之后将会话关闭。让我们来看一个简单的例子,假设我们有一个接口BussinessObject:
-
public interface BusinessObject {
-
public void doSomethingThatInvolvesDaos();
-
}
类BusinessObjectImpl实现了BusinessObject接口:
-
public class BusinessObjectImpl implements BusinessObject {
-
public void doSomethingThatInvolvesDaos() {
-
// lots of logic that calls
-
//DAO classes Which access
-
//data objects lazily
-
}
-
}
通过在Spring应用程序上下文中的一些配置,我们可以让将调用BusinessObject的方法拦截下来,再令它的方法支持延迟加载。看看下面的一个程序片段:
-
<beans>
-
<bean id="hibernateInterceptor" class="org.springframework.orm.hibernate.HibernateInterceptor">
-
<property name="sessionFactory">
-
<ref bean="sessionFactory"/>
-
</property>
-
</bean>
-
<bean id="businessObjectTarget" class="com.acompany.BusinessObjectImpl">
-
<property name="someDAO"><ref bean="someDAO"/></property>
-
</bean>
-
<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
-
<property name="target"><ref bean="businessObjectTarget"/></property>
-
<property name="proxyInterfaces">
-
<value>com.acompany.BusinessObject</value>
-
</property>
-
<property name="interceptorNames">
-
<list>
-
<value>hibernateInterceptor</value>
-
</list>
-
</property>
-
</bean>
-
</beans>
当businessObject被调用的时候,HibernateInterceptor打开一个Hibernate会话,并将调用请求传递给BusinessObjectImpl对象。当BusinessObjectImpl执行完成后,HibernateInterceptor透明地关闭了会话。应用层的代码不用了解任何持久层逻辑,还是实现了延迟加载。
单元测试中测试延迟加载
最后,我们需要用J-Unit来测试我们的延迟加载程序。我们可以轻易地通过重写TestCase类中的setUp和tearDown方法来实现这个要求。我比较喜欢用这个方便的抽象类作为我所有测试类的基类。
-
public abstract class MyLazyTestCase extends TestCase {
-
private SessionFactory sessionFactory;
-
private Session session;
-
public void setUp() throws Exception {
-
super.setUp();
-
SessionFactory sessionFactory = (SessionFactory) getBean("sessionFactory");
-
session = SessionFactoryUtils.getSession(sessionFactory, true);
-
Session s = sessionFactory.openSession();
-
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(s));
-
}
-
protected Object getBean(String beanName) {
-
//Code to get objects from Spring application context
-
}
-
public void tearDown() throws Exception {
-
super.tearDown();
-
SessionHolder holder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
-
Session s = holder.getSession();
-
s.flush();
-
TransactionSynchronizationManager.unbindResource(sessionFactory);
-
SessionFactoryUtils.closeSessionIfNecessary(s, sessionFactory);
-
}
-
}
很多使用WEB的朋友都喜欢利用openSessionView来加载这个问题。不错我也喜欢用,简单方便。但是说几种情况:
1.在quartz中总不能使用openSessionView模式吧
2.在spring后拦截的过程中,如果取拦截对象中的子对象利用openSessionView也不行。
还有很多情况是不能用的,那么怎么解决呢。有人说那就lazy=False吧,你想在单纯的解决一个quartz的时候调整整个WEB工程的加载好像不合适吧。
那怎么办呢,我觉得这个时候就可以考虑利用SQL语句使用这个重新加载一遍这个代理类,这样在想调用的时候才手动加载比较合适和方便吧,但是估计要多几行代码,好好考虑
这样你是拿不到的,因为这时候session已经关闭了,主要由于你的one-to-many中lazy设置为true,如果确实需要延迟加载,可以在session没关闭前,调用Hibernate.initialize(pojo.getChildren());或者pojo.getChildren().siz(),让集合代理类初始化,并加载关联对象集合,这一动作也要在session关闭之前操作。
美文美图
文章来源: shq5785.blog.csdn.net,作者:No Silver Bullet,版权归原作者所有,如需转载,请联系作者。
原文链接:shq5785.blog.csdn.net/article/details/51998756