学海拾遗

生活、技术、思想无处不在学习
posts - 52, comments - 23, trackbacks - 0, articles - 3
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

关于Spring和Hibernate整合后的懒加载和DAO模式应用,尤其介绍了其中的Hibernate懒加载在应用Spring的情况下在各个层次实现的情况。暂时是英文的,有时间试着翻译一下!~

Hibernate and Lazy Initialization

Hibernate object relational mapping offers both lazy and non-lazy modes of object initialization. Non-lazy initialization retrieves an object and all of its related objects at load time. This can result in hundreds if not thousands of select statements when retrieving one entity. The problem is compounded when bi-directional relationships are used, often causing entire databases to be loaded during the initial request. Of course one could tediously examine each object relationship and manually remove those most costly, but in the end, we may be losing the ease of use benefit sought in using the ORM tool.

The obvious solution is to employ the lazy loading mechanism provided by hibernate. This initialization strategy only loads an object's one-to-many and many-to-many relationships when these fields are accessed. The scenario is practically transparent to the developer and a minimum amount of database requests are made, resulting in major performance gains. One drawback to this technique is that lazy loading requires the Hibernate session to remain open while the data object is in use. This causes a major problem when trying to abstract the persistence layer via the Data Access Object pattern. In order to fully abstract the persistence mechanism, all database logic, including opening and closing sessions, must not be performed in the application layer. Most often, this logic is concealed behind the DAO implementation classes which implement interface stubs. The quick and dirty solution is to forget the DAO pattern and include database connection logic in the application layer. This works for small applications but in large systems this can prove to be a major design flaw, hindering application extensibility.

Being Lazy in the Web Layer

Fortunately for us, the Spring Framework has developed an out of box web solution for using the DAO pattern in combination with Hibernate lazy loading. For anyone not familiar with using the Spring Framework in combination with Hibernate, I will not go into the details here, but I encourage you to read Hibernate Data Access with the Spring Framework. In the case of a web application, Spring comes with both the OpenSessionInViewFilter and the OpenSessionInViewInterceptor. One can use either one interchangeably as both serve the same function. The only difference between the two is the interceptor runs within the Spring container and is configured within the web application context while the Filter runs in front of Spring and is configured within the web.xml. Regardless of which one is used, they both open the hibernate session during the request binding this session to the current thread. Once bound to the thread, the open hibernate session can transparently be used within the DAO implementation classes. The session will remain open for the view allowing lazy access to the database value objects. Once the view logic is complete, the hibernate session is closed either in the Filter doFilter method or the Interceptor postHandle method. Below is an example of the configuration of each component:

Interceptor Configuration

 1 <beans>
 2   <bean id="urlMapping"     
 3      class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">    
 4        <property name="interceptors">
 5          <list>
 6               <ref bean="openSessionInViewInterceptor"/>
 7          </list>
 8        </property>
 9        <property name="mappings">
10   
11   </bean>
12   
13   <bean name="openSessionInViewInterceptor"  
14     class="org.springframework.orm.hibernate.support.OpenSessionInViewInterceptor">
15        <property name="sessionFactory"><ref bean="sessionFactory"/></property>
16   </bean>
17 </beans>

Filter Configuration

 1 <web-app>                           
 2   <filter>
 3     <filter-name>hibernateFilter</filter-name>
 4     <filter-class>
 5       org.springframework.orm.hibernate.support.OpenSessionInViewFilter
 6     </filter-class>
 7    </filter>
 8         
 9   <filter-mapping>
10     <filter-name>hibernateFilter</filter-name>
11      <url-pattern>*.spring</url-pattern>
12   </filter-mapping>
13   
14 </web-app>            

Implementing the Hibernate DAO's to use the open session is simple. In fact, if you are already using the Spring Framework to implement your Hibernate DAO's, most likely you will not have to change a thing. The DAO's must access Hibernate through the convenient HibernateTemplate utility, which makes database access a piece of cake. Below is an example DAO.

Example DAO

 1  public class HibernateProductDAO extends HibernateDaoSupport implements ProductDAO {
 2                                 
 3        public Product getProduct(Integer productId) {
 4               return (Product)getHibernateTemplate().load(Product.class, productId);
 5        }
 6 
 7        public Integer saveProduct(Product product) {
 8               return (Integer) getHibernateTemplate().save(product);
 9        }       
10 
11        public void updateProduct(Product product) {
12               getHibernateTemplate().update(product);
13        }
14  }               

Being Lazy in the Business Layer

Even outside the view, the Spring Framework makes it easy to use lazy load initialization, through the AOP interceptor HibernateInterceptor. The hibernate interceptor transparently intercepts calls to any business object configured in the Spring application context, opening a hibernate session before the call, and closing the session afterward. Let's run through a quick example. Suppose we have an interface BusinessObject:

1 public interface BusinessObject {
2 
3     public void doSomethingThatInvolvesDaos(); 
4 }             

The class BusinessObjectImpl implements BusinessObject:

1 public class BusinessObjectImpl implements BusinessObject {
2 
3     public void doSomethingThatInvolvesDaos() {
4         // lots of logic that calls
5         // DAO classes Which access 
6         // data objects lazily
7     }
8 }              

Through some configurations in the Spring application context, we can instruct the HibernateInterceptor to intercept calls to the BusinessObjectImpl allowing it's methods to lazily access data objects. Take a look at the fragment below:

 1 <beans>
 2     <bean id="hibernateInterceptor" class="org.springframework.orm.hibernate.HibernateInterceptor">
 3          <property name="sessionFactory">
 4            <ref bean="sessionFactory"/>
 5          </property>
 6     </bean>
 7     <bean id="businessObjectTarget" class="com.acompany.BusinessObjectImpl">
 8        <property name="someDAO"><ref bean="someDAO"/></property>
 9     </bean>
10     <bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
11          <property name="target"><ref bean="businessObjectTarget"/></property>
12          <property name="proxyInterfaces">
13            <value>com.acompany.BusinessObject</value>
14          </property>
15          <property name="interceptorNames">
16            <list>
17               <value>hibernateInterceptor</value>
18            </list>
19          </property>
20      </bean>            
21 </beans>               

When the businessObject bean is referenced, the HibernateInterceptor opens a hibernate session and passes the call onto the BusinessObjectImpl. When the BusinessObjectImpl has finished executing, the HibernateInterceptor transparently closes the session. The application code has no knowledge of any persistence logic, yet it is still able to lazily access data objects.

Being Lazy in your Unit Tests

Last but not least, we'll need the ability to test our lazy application from J-Unit. This is easily done by overriding the setUp and tearDown methods of the TestCase class. I prefer to keep this code in a convenient abstract TestCase class for all of my tests to extend.

 1 public abstract class MyLazyTestCase extends TestCase {
 2 
 3         public void setUp() throws Exception {
 4                 super.setUp();
 5                 SessionFactory sessionFactory = (SessionFactory) getBean("sessionFactory");
 6                 Session s = sessionFactory.openSession();
 7                 TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(s));
 8 
 9         }
10 
11         protected Object getBean(String beanName) {
12             //Code to get objects from Spring application context
13         }
14 
15         public void tearDown() throws Exception {
16                 super.tearDown();
17                 SessionHolder holder = (SessionHolder) TransactionSynchronizationManager.
18                                getResource(sessionFactory);
19                Session s = holder.getSession(); 
20                s.flush();
21                TransactionSynchronizationManager.unbindResource(sessionFactory);
22                SessionFactoryUtils.closeSessionIfNecessary(s, sessionFactory);
23        }
24}
Traceback: http://jroller.com/kbaum/entry/orm_lazy_initialization_with_dao

在这里我试着翻译一下,有部分地方改变了一下,希望看到的朋友多多指正。

Hibernate和延迟装载

Hibernate ORM(关系-对象映射)针对对象的初始化分别提供了延迟和非延迟模式。非延迟装载将在读取时获得对象和其所关联的所有对象,这样当获取一个实体对象时,将会导致成千上万的select语句产生。这个问题在使用双向关联的时候将更加混乱,经常会导致在初始化请求就需要读取整个数据库。当然,某个人可以去繁重地检查每一个对象之间的关系,并且花费更多的代价去手动地移除它们。但是最终,我们可能已经失去了使用ORM工具换来的易用性。

因此,显而易见的解决方案是使用Hibernate提供的延迟加载机制。这种初始化策略仅在当那些字段访问时读取这个对象的一对多和多对多关系。这种情况对于开发者实际上是透明的,并且只创建一小部分数量的数据库请求,这样就可以很大地提升性能。这种技术的一个缺点就是当这个数据对象在使用时,延迟加载就需要Hibernate session一直打开。这就导致了一个主要问题:当尝试通过DAO(数据访问对象)模式来抽象化持久层时,为了完整地抽象持久化机制,所有的数据库逻辑,包括打开和关闭session,都将不会被应用层调用执行。最经常的情况,这些逻辑被后面的实现某些DAO接口的类来关注。但是,这个快速但不好的解决方案违反了DAO模式,并且在应用层包含了数据库连接逻辑。这样做对于小型应用可能适合,但是在大型系统中,这会产生一个重大的设计失误,阻止了应用的扩展。

在WEB层上延迟

我们是幸运的,Spring框架开发了一个开箱即用(out of box)的WEB解决方案,用来在组合Hibernate延迟加载时使用DAO模式。对于不熟悉使用Spring如何组合Hibernate的人,在这里我不会进入详细的细节,但是我鼓励你去阅读使用Spring框架进行Hibernate数据访问Hibernate Data Access with the Spring Framework》。就一个WEB应用来说,Spring提供了OpenSessionInViewFilterOpenSessionInViewInterceptor,一个可替换另一个使用而得到同样的功能。两者的唯一区别就是拦截器(Interceptor)在Spring容器中运行并且在WEB应用程序上下文(Application Context)中被配置,而过滤器(Filter)运行Spring之前并且是在web.xml中配置的。不管使用哪一个,它们都在绑定了session到当前线程的请求中打开Hibernate Session。一旦绑定到了线程,打开的Hibernate Session就能够透明地在DAO实现类中使用了。这个Session将一直打开,为视图(view)提供对数据库值对象的延迟访问。视图逻辑如果完成了,Hibernate Session就会在过滤器的doFilter或拦截器的postHandle方法中关闭。下面是各个部件配置的一个例子:

拦截器配置

 1 <beans>
 2   <bean id="urlMapping"     
 3      class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">    
 4        <property name="interceptors">
 5          <list>
 6               <ref bean="openSessionInViewInterceptor"/>
 7          </list>
 8        </property>
 9        <property name="mappings">
10   
11   </bean>
12   
13   <bean name="openSessionInViewInterceptor"  
14     class="org.springframework.orm.hibernate.support.OpenSessionInViewInterceptor">
15        <property name="sessionFactory"><ref bean="sessionFactory"/></property>
16   </bean>
17 </beans>

过滤器配置

 1 <web-app>                           
 2   <filter>
 3     <filter-name>hibernateFilter</filter-name>
 4     <filter-class>
 5       org.springframework.orm.hibernate.support.OpenSessionInViewFilter
 6     </filter-class>
 7    </filter>
 8         
 9   <filter-mapping>
10     <filter-name>hibernateFilter</filter-name>
11      <url-pattern>*.spring</url-pattern>
12   </filter-mapping>
13   
14 </web-app>            

实现Hibernate DAO来使用打开的Session很简单。实际上,如果你准备用Spring框架来实现你的Hibernate DAO,最可能的是你不需要改变任何一处代码。强制DAO必须通过方便的HibernateTemplate工具来访问Hibernate,将会使得数据库访问是小菜一碟。下面是一个DAO的例子。

DAO例子

 1  public class HibernateProductDAO extends HibernateDaoSupport implements ProductDAO {
 2                                 
 3        public Product getProduct(Integer productId) {
 4               return (Product)getHibernateTemplate().load(Product.class, productId);
 5        }
 6 
 7        public Integer saveProduct(Product product) {
 8               return (Integer) getHibernateTemplate().save(product);
 9        }       
10 
11        public void updateProduct(Product product) {
12               getHibernateTemplate().update(product);
13        }
14  }               

在业务层延迟

即使是在视图外面,Spring框架通过AOP拦截器HibernateInterceptor,也让延迟加载初始化变得很容易使用。这个Hibernate拦截器透明地拦截在Spring应用程序上下文(Application Context)中配置的任何业务对象的调用,在调用前打开一个Hibernate Session,同时在调用后关闭Session。让我们通过一个快速的例子来运行一下。假设我们有一个BusinessObject接口:

1 public interface BusinessObject {
2 
3     public void doSomethingThatInvolvesDaos(); 
4 }             

BusinessObjectImpl类实现了BusinessObject接口:

1 public class BusinessObjectImpl implements BusinessObject {
2 
3     public void doSomethingThatInvolvesDaos() {
4         // lots of logic that calls
5         // DAO classes Which access 
6         // data objects lazily
7     }
8 }              

通过在Spring应用程序上下文(Application Context)中的一些配置,我们能构造HibernateInterceptor来拦截BusinessObjectImpl的调用,以允许它的方法延迟访问数据对象。看一看下面的代码片断:

 1 <beans>
 2     <bean id="hibernateInterceptor" class="org.springframework.orm.hibernate.HibernateInterceptor">
 3          <property name="sessionFactory">
 4            <ref bean="sessionFactory"/>
 5          </property>
 6     </bean>
 7     <bean id="businessObjectTarget" class="com.acompany.BusinessObjectImpl">
 8        <property name="someDAO"><ref bean="someDAO"/></property>
 9     </bean>
10     <bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
11          <property name="target"><ref bean="businessObjectTarget"/></property>
12          <property name="proxyInterfaces">
13            <value>com.acompany.BusinessObject</value>
14          </property>
15          <property name="interceptorNames">
16            <list>
17               <value>hibernateInterceptor</value>
18            </list>
19          </property>
20      </bean>            
21 </beans>               

当businessObject bean被引用时,HibernateInterceptor打开一个Hibernate Session并且将调用传递给BusinessObjectImpl。当BusinessObjectImpl完成执行时,HibernateInterceptor透明地关闭这个Session。应用程序代码没有任何的持久化逻辑,然而它也能够延迟地访问数据对象。

在你的单元测试中延迟

最后一项重点是,我们需要能够从Junit中测试我们的延迟应用的能力。这很容易通过覆盖用例测试(TestCase)类中的setUp和tearDown方法做到。我更趋向于将这些代码放于一个方便的能被我所有的测试继承的抽象测试用例(TestCase)类中。

 

 1 public abstract class MyLazyTestCase extends TestCase {
 2 
 3         public void setUp() throws Exception {
 4                 super.setUp();
 5                 SessionFactory sessionFactory = (SessionFactory) getBean("sessionFactory");
 6                 Session s = sessionFactory.openSession();
 7                 TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(s));
 8 
 9         }
10 
11         protected Object getBean(String beanName) {
12             //Code to get objects from Spring application context
13         }
14 
15         public void tearDown() throws Exception {
16                 super.tearDown();
17                 SessionHolder holder = (SessionHolder) TransactionSynchronizationManager.
18                                getResource(sessionFactory);
19                Session s = holder.getSession(); 
20                s.flush();
21                TransactionSynchronizationManager.unbindResource(sessionFactory);
22                SessionFactoryUtils.closeSessionIfNecessary(s, sessionFactory);
23        }
24}


只有注册用户登录后才能发表评论。


网站导航: