Spring整合ORM技術 -- 集成Hibernate

1、Spring整合ORM方案的好處:

  • 方便基礎設施的搭建。不同的ORM技術都有一套自己的方案以初始化框架、搭建基礎設施等。在搭建基礎設施中,數據源是不可或缺的資源,不同的ORM框架的實現方式各不相同。Spring針對不同的ORM框架,採用相同的方式配置數據源,併爲不同的ORM框架提供相同的FactoryBean,用以初始化ORM框架的基礎設施,可以把它們當成普通Bean對待。

  • 異常封裝。Spring能夠轉化各種ORM框架拋出的異常,將ORM框架專有的或檢查型異常轉換爲SpringDAO異常體系中的標準異常。這樣用戶就可以有選擇地在適當的地方處理感興趣的異常,忽略不可恢復的異常。

  • 統一的事務管理。通過使用基於SpringDAO模板編程風格,甚至使用原生的ORM框架的API,只要遵循Spring所提出的少量編程要求,就可以使用Spring提供的事務管理功能。Spring爲不同的ORM框架提供了對應的事務管理器,可用聲明式事務管理,並且透明地實現本地事務管理到全局JTA事務管理的切換。

  • 允許混合使用多個ORM框架。Spring在DAO層異常、事務、資源等高級層次建立了抽象,可以讓業務層對DAO具體實現的技術不敏感。這樣開發者就可以在底層選用合適的實現方式,甚至可以混合使用多種ORM,一般的CRUD使用Hibernate,而數據查詢使用iBatis或SpringJDBC。

  • 方便單元測試。Spring容器使得替換不同的實現和配置方式變得很簡單,這有利於單元測試

2、Spring整合Hibernate步驟

    2.1 配置SessionFactory(可自動完成)

        使用Hibernate框架的第一個工作是編寫Hibernate的配置文件,接着使用這些配置文件實例化SessionFactory,創建好Hibernate的基礎設施。Spring爲創建SessionFactory提供了FactoryBean工廠類:org.springframework.orm.hibernate3.LocalSessionFactoryBean,通過一些必要的配置,即可或缺一個SessionFactoryBean。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:p="http://www.springframework.org/schema/p"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:aop="http://www.springframework.org/schema/aop" 
 xmlns:tx="http://www.springframework.org/schema/tx"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.1.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
    
    <context:component-scan base-package="org.worm.biz.springmvc"/>
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
     destroy-method="close"
     p:driverClassName="${jdbc.driverClassName}"
     p:url="${jdbc.url}"
     p:username = "${jdbc.username}"
     p:password = "${jdbc.password}"/>
    <!-- 也可以使用p:mappingDirectoryLocations來指定多個放置Hibernate映入文件的目錄 --> 
    <bean id="sessionFactory" class = "org.springframework.orm.hibernate3.LocalSessionFactoryBean"
     p:dataSource-ref = "dataSource"
     p:mappingLocations="classpath:org/worm/biz/springmvc/dao/*.hbm.xml">
     <!-- 指定Hibernate配置文件 -->
     <property name="hibernateProperties">
      <props>
       <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
       <prop key="hibernate.show_sql">true</prop>
       <prop key="hibernate.formate_sql">true</prop>
      </props>
     </property> 
    </bean>
    
    <!-- 配置HibernateTemplate Bean -->
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate"
     p:sessionFactory-ref="sessionFactory"/>
    <!-- 配置Hibernate事務管理 -->
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"
     p:sessionFactory-ref="sessionFactory"/>
    <tx:annotation-driven transaction-manager="transactionManager"/>
    </beans>

    2.2、使用HibernateTemplate

        基於模板類使用Hibernate是最簡單的方式,它可以在不犧牲Hibernate強大功能的情況下,以一種更簡潔的方式使用Hibernate,極大地降低了Hibernate的使用難度。

package org.worm.biz.springmvc.dao.hibernate;
import java.util.List;
public interface IBaseDao<T> {
 /**
  * 保存實體對象
  * @param t 實體對象
  * */
 public void addEntity(T t);
 
 /**
  * 更新實體對象
  * @param t 實體對象
  * */
 public  void updateEntity(T t);
 
 /**
  * 獲取實體對象
  * @param serialNo 主鍵
  * */
 public  T getEntity(String serialNo,T t);
 
 /**
  * 獲取實體對象集
  * @param hql 查詢語句
  * @param params 查詢條件
  * */
 public  List<T> getEntities(String hql,Object[] params);
 
 /**
  * 獲取實體對象集
  * */
 public  List<T> getEntitiesByExample(T t);
}

package org.worm.biz.springmvc.dao.hibernate;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateTemplate;
public class BaseDao<T> implements IBaseDao<T> {
 private Class entityClass;//Dao的泛型類型,即子類所指定的T所對應的類型
 
 public BaseDao() {
  Type genType = getClass().getGenericSuperclass();
  Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
  entityClass = (Class) params[0];
 }
 @Autowired
 private HibernateTemplate hibernateTemplate;
 public HibernateTemplate getHibernateTemplate() {
  return hibernateTemplate;
 }
 public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
  this.hibernateTemplate = hibernateTemplate;
 }
 
 @Override
 public void addEntity(T t) {
  // TODO Auto-generated method stub
  getHibernateTemplate().save(t);
 }
 @Override
 public  void updateEntity(T t) {
  // TODO Auto-generated method stub
  getHibernateTemplate().update(t);
 }
 @Override
 public  T getEntity(String serialNo) {
  // TODO Auto-generated method stub
  return (T) getHibernateTemplate().get(entityClass, Integer.valueOf(serialNo));
 }
 @Override
 public  List<T> getEntities(String hql, Object[] params) {
  // TODO Auto-generated method stub
  return (List<T>) getHibernateTemplate().find(hql, params);
 }
 @Override
 public  List<T> getEntitiesByExample(T t) {
  // TODO Auto-generated method stub
  return getHibernateTemplate().findByExample(t);
 }
}

package org.worm.biz.springmvc.dao.hibernate;
import org.springframework.stereotype.Repository;
import org.worm.biz.springmvc.dao.User;
@Repository
public class UserDao extends BaseDao<User>{
 //添加自身獨有的業務
}

    2.3、使用Hibernate原生Hibernate API

        使用Hibernate原生API與在Spring中使用HibernateTemplate中使用和事務綁定的Session相同

package org.worm.biz.springmvc.dao.hibernate;
import java.util.List;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.worm.biz.springmvc.dao.User;
public class BaseDao implements IBaseDao<User> {
// @Autowired
// private HibernateTemplate hibernateTemplate;
//
// public HibernateTemplate getHibernateTemplate() {
//  return hibernateTemplate;
// }
//
// public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
//  this.hibernateTemplate = hibernateTemplate;
// }
 @Autowired
 private SessionFactory sessionFactory;
 
 @Override
 public void addEntity(User t) {
  // TODO Auto-generated method stub
//  getHibernateTemplate().save(t);
  sessionFactory.getCurrentSession().save(t);
 }
 @Override
 public  void updateEntity(User t) {
  // TODO Auto-generated method stub
//  getHibernateTemplate().update(t);
  sessionFactory.getCurrentSession().update(t);
 }
 @Override
 public  User getEntity(String serialNo, User t) {
  // TODO Auto-generated method stub
//  return (User) getHibernateTemplate().get(t.getClass(), Integer.valueOf(serialNo));
  return (User) sessionFactory.getCurrentSession().get(t.getClass(), Integer.valueOf(serialNo));
 }
 @Override
 public  List<User> getEntities(String hql, Object[] params) {
  // TODO Auto-generated method stub
//  return (List<User>) getHibernateTemplate().find(hql, params);
  return null;
 }
 @Override
 public  List<User> getEntitiesByExample(User t) {
  // TODO Auto-generated method stub
//  return getHibernateTemplate().findByExample(t);
  return null;
 }
}

    2.4、使用註解配置Entity

package org.worm.biz.springmvc.dao;
import javax.persistence.*;

@Entity
//@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Table(name = "t_user")
public class User{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_no")
    protected int userId;
    @Column(name = "user_nick_name")
    protected String userName;
    protected String password;
    
    @Column(name = "user_age")
    protected String userAge;
    
    public int getUserId() {
        return userId;
    }
    public void setUserId(int userId) {
        this.userId = userId;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
 public String getUserAge() {
  return userAge;
 }
 public void setUserAge(String userAge) {
  this.userAge = userAge;
 }
    
}

    Hibernate通過AnnotationConfiguration的addAnnotatedClass()方法加載使用JPA註解的實體類,獲取映射的元數據信息, 並在此基礎上創建SessionFactory實例。AnnotationSessionFactoryBean擴展了LocalSessionFactoryBean類,增強的功能是:可以根據實體類的註解獲取ORM的配置信息。也允許混合使用XML配置和註解配置對象關係映射,Hibernate內部自動整合這些元數據信息,不會產生衝突。Spring爲了可以通過掃描方式加載帶註解的實體類,提供了一個好用的packagesToScan屬性,可以指定一系列的包名,Spring將掃描並加載這些包(包含子包)路徑的所有帶註解實體類。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:p="http://www.springframework.org/schema/p"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:aop="http://www.springframework.org/schema/aop" 
 xmlns:tx="http://www.springframework.org/schema/tx"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.1.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
    
    <context:component-scan base-package="org.worm.biz.springmvc"/>
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
     destroy-method="close"
     p:driverClassName="${jdbc.driverClassName}"
     p:url="${jdbc.url}"
     p:username = "${jdbc.username}"
     p:password = "${jdbc.password}"/>
    <!-- 也可以使用p:mappingDirectoryLocations來指定多個放置Hibernate映入文件的目錄 --> 
<!--     <bean id="sessionFactory" class = "org.springframework.orm.hibernate3.LocalSessionFactoryBean" -->
<!--      p:dataSource-ref = "dataSource" -->
<!--      p:mappingLocations="classpath:org/worm/biz/springmvc/dao/*.hbm.xml"> -->
<!--      <property name="hibernateProperties"> -->
<!--       <props> -->
<!--        <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop> -->
<!--        <prop key="hibernate.show_sql">true</prop> -->
<!--        <prop key="hibernate.formate_sql">true</prop> -->
<!--       </props> -->
<!--      </property>  -->
<!--     </bean> -->
    <!-- 帶註解實體類 -->
    <bean id="sessionFactory_1" class = "org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
     p:dataSource-ref = "dataSource">
     <!-- 通過掃描方式加載帶註解的實體類 -->
     <property name="packagesToScan" value="org.worm.biz.springmvc.dao"/>
     <!-- 指定Hibernate配置文件 -->
     <property name="hibernateProperties">
      <props>
       <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
       <prop key="hibernate.show_sql">true</prop>
       <prop key="hibernate.formate_sql">true</prop>
      </props>
     </property> 
    </bean>
    <!-- 配置HibernateTemplate Bean -->
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate"
     p:sessionFactory-ref="sessionFactory"/>
    <!-- 配置Hibernate事務管理 -->
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"
     p:sessionFactory-ref="sessionFactory"/>
    <tx:annotation-driven transaction-manager="transactionManager"/>
    </beans>

    2.5 統一使用事務管理,詳情配置請見2.2

3、延遲加載問題

    Hibernate允許對關聯對象、屬性進行延遲加載,但是必須保證延遲加載的操作限於同一個Hibernate Session範圍之內進行。如果Service層返回一個啓用了延遲加載功能領域對象給Web層,當Web層訪問到那些需要延遲加載的數據時,由於加載領域對象的Hibernate Session已經關閉,將會導致延遲加載數據異常。因此Spring專門提供了一個OpenSessionInViewFilter過濾器,它的主要功能是使每個請求過程中綁定一個Hibernate Session,即使最初的事務已經完成了,可以在Web層進行延遲加載的操作。注意:對小型系統來說,使用OpenSessionInViewFilter確實可以降低延遲加載所引發的各種問題,使Service層代碼更易開發和維護,但是對大型且高併發的應用來說,強烈建議不要使用OpenSessionInViewFilter。因爲OpenSessionInViewFilter會讓每個Web請求線程都綁定一個Hibernate的Session。知道Web請求處理時纔會釋放。這極大的影響了系統新能。

4、DAO層設計

    爲了增加代碼複用率,強烈建議使用接口、基類和泛型來定義。詳情見2.2.基類BaseDao<T>通過泛型方式允許子類Dao指定操作的實體類,以便簡化常用的數據操作方法。同時,在BaseDao注入了一個HibernateTemplate,這樣子類只要打上@Repository的註解就自然擁有HibernateTemplate成員變量了,無須各自聲明。BaseDao的構造方法通過Java反射機制自動解析出子類T鎖對應的類型,以便HibernateTemplate直接利用這個信息進行數據訪問操作。

5、查詢接口方法的設計

    DAO層除了CRUD的數據操作之外,另外一個重要的操作就是根據查詢條件執行數據查詢,所有ORM框架都允許用戶動態綁定參數確定查詢條件。查詢條件的數目往往是不固定的。因此實體DAO定義帶參數的查詢方法一般有:

  • 每一個條件項參數對應一個入參。List<T> findOrder(String hql,Date starttime,Date endtime,int deptId);這種方法優點是含義清晰,可讀性強,內部邏輯簡單,但是接口穩定性差,並且如果查詢條件項過多,整個實體DAO類會顯得很臃腫笨重。

  • 使用數組傳遞條件項參數。List<T> findOrder(String hql,Object[] params);這種方法的確定是可讀性不強,調用者往往需要通過查看該接口的Javadoc才能正確使用。

  • 使用JDK5.0的不定參數。List<T> findOrder(String hql,Object... params);

  • 將查詢條件項參數封裝成對象。List<T> findOrder(String hql,OrderObject param);OrderObject查詢條件對象封裝了hql查詢語句可能會用到的條件項參數,在查詢方法內部,開發者必須判斷查詢條件對象的屬性並正確綁定條件項參數。確定是會造成類數目的膨脹,有事甚至一個實體DAO需要對應多個查詢條件參數類。

  • 使用Map傳遞條件項參數。List<T> findOrder(String hql,Map params)使用這樣方式,接口方法簽名可以在條件項發生變化的情況下保持穩定,同事通過鍵指定條件項參數名,這在一定程度上解決了接口的健壯性。

6、總結

    Spring爲其所支持的ORM框架提供了方面易用的FactoryBean,用以創建ORM框架的基礎設施。Spring通過模板類在不損失框架功能的情況下大大降低了使用這些ORM技術的難度。此外Spring允許用戶使用原生的API來構造DAO。使用原生API時,Spring能夠保證用戶獲取到事務綁定的資源,Spring的事務管理機制同樣有效。

    Spring對Hibernate所提供的支持應該是最豐富的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章