Spring 与 Hibernate 的延迟加载和 Dao 模式-阿里云开发者社区

开发者社区> 开发与运维> 正文

Spring 与 Hibernate 的延迟加载和 Dao 模式

简介: Hibernate 与延迟加载: Hibernate 对象关系映射提供延迟的与非延迟的对象初始化。非延迟加载在读取一个对象的时候会将与这个对象所有相关的其他对象一起读取出来。这有时会导致成百的(如果不是成千的话) select 语句在读取对象的时候执行。这个问题有时出现在使用双向关系的时候,经常会导致整个数据库都在初始化的阶段被读出来了。当然,你可以不厌其烦地检查每一个对象与其他对象的关系
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


Example 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 interceptor HibernateInterceptor 来使得延迟加载变得很容易实现。这个 Hibernate interceptor 透明地将调用配置在 Spring 应用程序上下文中的业务对象中方法的请求拦截下来,在调用方法之前打开一个 Hibernate 会话,然后在方法执行完之后将会话关闭。让我们来看一个简单的例子,假设我们有一个接口 BussinessObject


public interface BusinessObject {
public void doSomethingThatInvolvesDaos();
}
The class BusinessObjectImpl implements 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);
}
}

 

由于要求在项目中使用泛型的DAO,所以上网Google了一下,找到了IBM的一篇文章。文章讲得不错,但是有些地方不清楚,如果完全按照那篇文章可能还会遇到一些困难。所以写了这篇文章,解释如何在项目中加入泛型的DAO实现。

首先是总的类关系的UML图:

然后是在配置文件中的关系图:  

其中,IStaffDao是我们自己定义的接口,这个接口类似:

public   interface  IStaffDAO  extends  GenericDao < Staff, Integer >

public  List listAll(); 

public  Staff getByLogonAndId(String logon, Integer id); 

// more  

}
 

 

GenericDao<T , PK extends Serilizable> 是泛型的 Dao 接口:

/** */ /**
 * 2006-11-22
 * 范型DAO接口
 * 
@author  Zou Ang
 * Contact <a href ="mailto:richardeee@gmail.com">Zou Ang</a>
 
*/

public   interface  GenericDao < T, PK  extends  Serializable >   {

    
/** */ /**
     * 保存一个对象到数据库
     * 
@param  newInstance 需要保存的对象
     * 
@return
     
*/

    PK create(T newInstance);
    
/** */ /**
     * 从数据库读取一个对象
     * 
@param  id 主键
     * 
@return
     
*/

    T read(PK id);
    
    
/** */ /**
     * 更新一个对象
     * 
@param  transientObject 被更新的对象
     
*/

    
void  update(T transientObject);
    
    
/** */ /**
     * 删除一个对象
     * 
@param  transientObject 被删除的对象
     
*/

    
void  delete(T transientObject);
}

 

GenericDaoHibernateImpl GenericDao 接口的泛型实现 :

 


/** */ /**
 * 2006-11-22
 * 范型DAO实现
 * 
@author  Zou Ang
 * Contact <a href ="mailto:richardeee@gmail.com">Zou Ang</a>
 
*/

public   class  GenericDaoHibernateImpl < T,PK  extends  Serializable >  
    
extends  HibernateDaoSupport 
        
implements  GenericDao < T, PK >  ,FinderExecutor {
    
    
private  Class < T >  type;
    
private  FinderNamingStrategy namingStrategy  =   new  SimpleFinderNamingStrategy();  //  Default. Can override in config
     private  FinderArgumentTypeFactory argumentTypeFactory  =   new  SimpleFinderArgumentTypeFactory();  //  Default. Can override in config
    
    
public  GenericDaoHibernateImpl(Class < T >  type) {
        
this .type  =  type;
    }


    
/**/ /*  (non-Javadoc)
     * @see com.gdnfha.atcs.common.service.dao.GenericDao#create(java.lang.Object)
     
*/

    
public  PK create(T newInstance)  {
        
return  (PK)getHibernateTemplate().save(newInstance);
    }


    
/**/ /*  (non-Javadoc)
     * @see com.gdnfha.atcs.common.service.dao.GenericDao#delete(java.lang.Object)
     
*/

    
public   void  delete(T transientObject)  {
        getHibernateTemplate().delete(transientObject);
    }


    
/**/ /*  (non-Javadoc)
     * @see com.gdnfha.atcs.common.service.dao.GenericDao#read(java.io.Serializable)
     
*/

    
public  T read(PK id)  {
        
return  (T)getHibernateTemplate().get(type, id);
    }


    
/**/ /*  (non-Javadoc)
     * @see com.gdnfha.atcs.common.service.dao.GenericDao#update(java.lang.Object)
     
*/

    
public   void  update(T transientObject)  {
        getHibernateTemplate().update(transientObject);
    }


    
public  List < T >  executeFinder(Method method,  final  Object[] queryArgs)
    
{
        
final  Query namedQuery  =  prepareQuery(method, queryArgs);
        
return  (List < T > ) namedQuery.list();
    }


    
public  Iterator < T >  iterateFinder(Method method,  final  Object[] queryArgs)
    
{
        
final  Query namedQuery  =  prepareQuery(method, queryArgs);
        
return  (Iterator < T > ) namedQuery.iterate();
    }

    
    
private  Query prepareQuery(Method method, Object[] queryArgs)
    
{
        
final  String queryName  =  getNamingStrategy().queryNameFromMethod(type, method);
        
final  Query namedQuery  =  getSession().getNamedQuery(queryName);
        String[] namedParameters 
=  namedQuery.getNamedParameters();
        
if (namedParameters.length == 0 )
        
{
            setPositionalParams(queryArgs, namedQuery);
        }
  else   {
            setNamedParams(namedParameters, queryArgs, namedQuery);
        }

        
return  namedQuery;
    }


    
private   void  setPositionalParams(Object[] queryArgs, Query namedQuery)
    
{
        
//  Set parameter. Use custom Hibernate Type if necessary
         if (queryArgs != null )
        
{
            
for ( int  i  =   0 ; i  <  queryArgs.length; i ++ )
            
{
                Object arg 
=  queryArgs[i];
                Type argType 
=  getArgumentTypeFactory().getArgumentType(arg);
                
if (argType  !=   null )
                
{
                    namedQuery.setParameter(i, arg, argType);
                }

                
else
                
{
                    namedQuery.setParameter(i, arg);
                }

            }

        }

    }


    
private   void  setNamedParams(String[] namedParameters, Object[] queryArgs, Query namedQuery)
    
{
        
//  Set parameter. Use custom Hibernate Type if necessary
         if (queryArgs != null )
        
{
            
for ( int  i  =   0 ; i  <  queryArgs.length; i ++ )
            
{
                Object arg 
=  queryArgs[i];
                Type argType 
=  getArgumentTypeFactory().getArgumentType(arg);
                
if (argType  !=   null )
                
{
                    namedQuery.setParameter(namedParameters[i], arg, argType);
                }

                
else
                
{
                    
if (arg  instanceof  Collection)  {
                        namedQuery.setParameterList(namedParameters[i], (Collection) arg);
                    }

                    
else
                    
{
                        namedQuery.setParameter(namedParameters[i], arg);
                    }

                }

            }

        }

    }

    
    
public  FinderNamingStrategy getNamingStrategy()
    
{
        
return  namingStrategy;
    }


    
public   void  setNamingStrategy(FinderNamingStrategy namingStrategy)
    
{
        
this .namingStrategy  =  namingStrategy;
    }


    
public  FinderArgumentTypeFactory getArgumentTypeFactory()
    
{
        
return  argumentTypeFactory;
    }


    
public   void  setArgumentTypeFactory(FinderArgumentTypeFactory argumentTypeFactory)
    
{
        
this .argumentTypeFactory  =  argumentTypeFactory;
    }


}


FinderNamingStrategy 是查找方法的命名规范:

 

public   interface  FinderNamingStrategy
{
    
public  String queryNameFromMethod(Class findTargetType, Method finderMethod);
}


目前有两个命名查找策略,使用的是

 

public   class  SimpleFinderNamingStrategy  implements  FinderNamingStrategy
{
    
public  String queryNameFromMethod(Class findTargetType, Method finderMethod)
    
{
        
return  findTargetType.getSimpleName()  +   " . "   +  finderMethod.getName();
    }

}

 

FinderArgumentTypeFactory 目前还没有什么作用,主要是返回自定义的 Hibernate 类型:

 

public   class  SimpleFinderArgumentTypeFactory  implements  FinderArgumentTypeFactory
{
    
public  Type getArgumentType(Object arg)
    
{
//         if(arg instanceof Enum)
//         {
//             return getEnumType(arg.getClass());
//         }
//         else
//         {
             return   null ;
//         }
    }


//     private Type getEnumType(Class<? extends Object> argClass)
//     {
//         Properties p = new Properties();
//         p.setProperty("enumClassName", argClass.getName());
//         Type enumType = TypeFactory.heuristicType("org.hibernate.demo.EnumUserType", p);
//         return enumType;
//     }
}

 

FinderIntroductionAdvisor FinderIntroductionInterceptor:

 

public   class  FinderIntroductionAdvisor  extends  DefaultIntroductionAdvisor
{
    
public  FinderIntroductionAdvisor()
    
{
        
super ( new  FinderIntroductionInterceptor());
    }

}

public   class  FinderIntroductionInterceptor  implements  IntroductionInterceptor
{

    
public  Object invoke(MethodInvocation methodInvocation)  throws  Throwable
    
{

        FinderExecutor executor 
=  (FinderExecutor) methodInvocation.getThis();

        String methodName 
=  methodInvocation.getMethod().getName();
        
if (methodName.startsWith( " get " ||  methodName.startsWith( " list " ))
        
{
            Object[] arguments 
=  methodInvocation.getArguments();
            
return  executor.executeFinder(methodInvocation.getMethod(), arguments);
        }

        
else   if (methodName.startsWith( " iterate " ))
        
{
            Object[] arguments 
=  methodInvocation.getArguments();
            
return  executor.iterateFinder(methodInvocation.getMethod(), arguments);
        }

//         else if(methodName.startsWith("scroll"))
//         {
//             Object[] arguments = methodInvocation.getArguments();
//             return executor.scrollFinder(methodInvocation.getMethod(), arguments);
//         }
         else
        
{
            
return  methodInvocation.proceed();
        }

    }


    
public   boolean  implementsInterface(Class intf)
    
{
        
return  intf.isInterface()  &&  FinderExecutor. class .isAssignableFrom(intf);
    }

}


然后就到了配置文件了:

 

       <!--   Start :范型DAO配置   -->  
     
<  bean   id  ="abstractDaoTarget"  
        class 
="com.gdnfha.atcs.common.service.dao.hibernate.GenericDaoHibernateImpl"  
        abstract 
="true"   >  
         
<  property   name  ="sessionFactory"   >  
             
<  ref   local  ="sessionFactory"     />  
         
</  property  >  
         
<  property   name  ="namingStrategy"   >  
             
<  ref   bean  ="simpleFinderNamingStratrgy"     />  
         
</  property  >  
     
</  bean  >  
 
     
<  bean   id  ="abstractDao"  
        class 
="org.springframework.aop.framework.ProxyFactoryBean"  
        abstract 
="true"   >  
         
<  property   name  ="interceptorNames"   >  
             
<  list  >  
                 
<  value  >  finderIntroductionAdvisor  </  value  >  
             
</  list  >  
         
</  property  >  
     
</  bean  >  
 
     
<  bean   id  ="finderIntroductionAdvisor"  
        class 
="com.gdnfha.atcs.common.service.dao.finder.FinderIntroductionAdvisor"     />  
 
     
<  bean   id  ="namingStrategy"  
        class 
="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"   >  
         
<  property   name  ="staticField"   >  
             
<  value  >  org.hibernate.cfg.ImprovedNamingStrategy.INSTANCE  </  value  >  
         
</  property  >  
     
</  bean  >  
 
     
<  bean   id  ="extendedFinderNamingStrategy"  
        class 
="com.gdnfha.atcs.common.service.dao.finder.impl.ExtendedFinderNamingStrategy"     />  
        
     
<  bean   id  ="simpleFinderNamingStratrgy"   class  ="com.gdnfha.atcs.common.service.dao.finder.impl.SimpleFinderNamingStrategy"   />  
      
<!--   End: 范型DAO配置   -->  
 
     
<!--   Start: 测试范型DAO   -->  
  
     
<  bean   id  ="staffDao"   parent  ="abstractDao"   >  
         
<  property   name  ="proxyInterfaces"   >  
             
<  value  >  com.gdnfha.atcs.maintain.service.dao.IStaffDAO      </  value  >  
         
</  property  >  
         
<  property   name  ="target"   >  
             
<  bean   parent  ="abstractDaoTarget"   >  
                 
<  constructor-arg  >  
                     
<  value  >  com.gdnfha.atcs.common.pojo.Staff  </  value  >  
                 
</  constructor-arg  >  
             
</  bean  >  
         
</  property  >  
     
</  bean  >  
 
     
<!--    End:测试范型DAO   -->  


还要在Staff.hbm.xml中配置:

 

 

<  query   name  ="Staff.getByLogonAndId"   >   
        
<![CDATA[  select s from Staff s where s.staffLogon = ? and s.staffId = ?   ]]>   
</  query  >  

这里要特别注意<query></query>这个要写在<class></class>的外面,否则会提示Mapping Exception:No Named Query

好了,大公告成了!现在可以跟以前一样使用

 

staffDao.read(new Integer(1));

staffDao.getByLogonAndId("abc",new Integer(2));

 

appContext.getBean("staffDao"); 这样进行测试了 Simple 的,也就是直接是 < 类型名 >.< 方法名 > 的形式。 
 

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章