Spring對Hibernate的支持

http://subclipse.tigris.org/update

 

精通Spring——Java輕量級架構開發實踐 10.4 Spring對Hibernate的支持 
http://book.csdn.net/ 2006-8-15 16:27:00
圖書導讀 當前章節:10.4 Spring對Hibernate的支持·4.2 工廠模式(Design Pattern:Factory Method)的精髓·4.3 單例模式(Design Pattern:Singleton)·10.3 Spring對IBatis的支持·10.5 小結·18.1 模仿對象·18.2 Spring Mock簡介Java EE開源框架培訓
藍點世紀外企高端Java軟件工程師培訓助你實現月薪 1000-6000元的飛躍!4個月積累1年的經驗值
www.fsailing.com/
Web2.0 個人桌面
i桌面,與微軟Live爭鋒 網絡桌面新概念
live.csdn.net
Oracle身份管理與薩班斯法案
Oracle身份管理的重要意義在哪裏? 獲得Oracle 10最新免費資源
ad.cn.doubleclick.ne...

如上文所述,Spring對DAO模式具有概念和風格上的一致性,所以無論使用哪種持久方案(比如JdbcTemplate、SqlMapClientTemplate以及稍後將要介紹的HibernateTemplate)都會感到非常親切。

下文將通過Spring改寫上一章給出的HibernateStockDao,並向其中添加一些額外的功能。

說明:本書中使用的的是Hibernate3,所以也將使用Spring對於Hibernate3支持的那部分功能(當然,Spring對早期版本的Hibernate也提供支持)。

10.4.1  在Spring上下文中配置SessionFactory
通過上文的描述,可以知道,Spring使用JdbcTemplate時必須和特定的數據源進行綁定。而在Hibernate中,數據源是對用戶屏蔽的,它使用一個稱爲“Session”的強勁武器。

Session具有建立或取消對象的持久關聯、同步對象狀態和數據庫以及事務管理等等複雜功能。Session是Hibernate的核心概念,就如同SqlMap與之IBatis一樣。Session的創建依賴於Hibernate的SessionFactory,SessionFactory和Session一樣,也是Hibernate的核心組件。

和IBatis的整合方式一樣,Spring會將Hibernate基本配置文件中的數據源屬性抽離到Spring自身的配置文件中,以提供統一的數據源管理和注射。

首先,給出Hibernate的配置文件,如代碼10.19所示。

代碼10.19  hibernate.cfg.xml

 

<?xml version='1.0' encoding='utf-8'?>

<!DOCTYPE hibernate-configuration PUBLIC

        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"

        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

 

<hibernate-configuration>

 

  <session-factory>

<!-- Database connection settings -->

<!--    <property name="connection.driver_class">-->

<!--      org.postgresql.Driver-->

<!--    </property>-->

<!--    <property name="connection.url">-->

<!--      jdbc:postgresql://1210.0.0.1:5432/hibernate-->

<!--    </property>-->

<!--    <property name="connection.username">postgres</property>-->

<!--    <property name="connection.password">1111</property>-->

 

    <!-- SQL dialect -->

    <property name="dialect">

      org.hibernate.dialect.PostgreSQLDialect

    </property>

    <!-- Echo all executed SQL to stdout -->

    <property name="show_sql">true</property>

    <!-- Drop and re-create the database schema on startup -->

    <property name="hbm2ddl.auto">create</property>

 

    <mapping resource="chapter7/hibernate/domain/Category.hbm.xml" />

    <mapping resource="chapter7/hibernate/domain/Product.hbm.xml" />

 

  </session-factory>

</hibernate-configuration>

注意,代碼10.19中被註釋掉的部分,它將被抽離到了Spring的上下文配置文件,而其餘部分則暫時保持不變。

下面給出Spring的配置文件,如代碼10.20所示。

代碼10.20  dataAccessContext-local.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

"http://www.springframework.org/dtd/spring-beans.dtd">

 

<beans>

  <bean id="propertyConfigurer"

class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

    <property name="locations">

      <list>

        <value>classpath:jdbc.properties</value>

      </list>

    </property>

  </bean>

 

  <bean id="dataSource"

class="org.apache.commons.dbcp.BasicDataSource"

destroy-method="close">

    <property name="driverClassName" value="${jdbc.driverClassName}"/>

    <property name="url"

    value="jdbc:postgresql://127.0.0.1:5432/hibernate"/>

    <property name="username" value="${jdbc.username}"/>

    <property name="password" value="${jdbc.password}"/>

  </bean>

 

  <bean id="sessionFactory"

class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

    <property name="dataSource" ref="dataSource"/>

    <property name="configLocations">

      <value>chapter7/spring/hibernate/hibernate.cfg.xml</value>

    </property>

<!--    <property name="mappingResources">-->

<!--      <list>-->

<!--        <value>chapter7/hibernate/domain/Category.hbm.xml</value>-->

<!--        <value>chapter7/hibernate/domain/Product.hbm.xml</value>-->

<!--      </list>-->

<!--    </property>-->

<!--    <property name="hibernateProperties">-->

<!--      <props>-->

<!--        <prop key="hibernate.dialect">

                 org.hibernate.dialect.PostgreSQLDialect

               </prop>-->

<!--        <prop key="show_sql">true</prop>-->

<!--        <prop key="hbm2ddl.auto">create</prop>-->

<!--      </props>-->

<!--    </property>-->

  </bean>

 

</beans>

可以發現,從代碼10.19抽離出的數據源,現在換成了Apache的DBCP連接池。當然,也可以換作其他實現,比如從一個JNDI上獲取的數據源:

  <bean id="dataSource"

  class="org.springframework.jndi.JndiObjectFactoryBean">

    <property name="jndiName" value="java:comp/env/jdbc/myds"/>

  </bean>

Spring提供了LocalSessionFactoryBean來創建本地的Hibernate SessionFactory,這就類似於上節中介紹的SqlMapClientFactoryBean(它用以創建IBatis的SqlMap)。當然也可以創建綁定在JNDI上的SessionFactory,不過這通常只在EJB環境下使用。

注意:代碼10.20中被註釋掉的部分,如果不使用LocalSessionFactoryBean的configLocations屬性讀取Hibernate的原生配置文件,可由Spring的LocalSessionFactoryBean負責配置Hibernate,它和hibernate.cfg.xml的作用完全一致,這時候就不需要Hibernate的原生配置了。

10.4.2  重建Hibernate進貨DAO僞實現
有了以上對Hibernate SessionFactory的正確配置後,下文將重建一個Hibernate StockDao(曾在第九章9.7節給出過),其中主要展示了HinbernateTemplate和HibernateCallback(這兩個類也是Spring給出的,它們用於簡化Hibernate的操作)的使用方法。

首先爲上一章代碼9.31的HibernateStockDao抽象出一個接口,並且增設一個查詢方法,如代碼10.21所示。

代碼10.21  StockDao.java

 

package chapter10.spring.hibernate;

 

import java.util.List;

import chapter10.hibernate.domain.Category;

 

public interface StockDao {

  public void createCategoryCascade(Category category);

  public void deleteCategoryCascade(Category category);

  /**

   * 通過Category,查找Product列表

   */

  public List findProductByCategory(Category category);

}

接着撰寫一個StockDao的僞實現,它暴露了一個HibernateTemplate屬性,用以接受注射,如代碼10.22所示。

代碼10.22  HibernateStockDao.java

package chapter10.spring.hibernate;

 

import java.util.List;

import org.springframework.orm.hibernate3.HibernateTemplate;

import chapter10.hibernate.domain.Category;

 

public class HibernateStockDao implements StockDao {

 

  private HibernateTemplate template;

 

  public void setTemplate(HibernateTemplate template) {

    this.template = template;

  }

 

  public void createCategoryCascade(Category category) {

  }

 

  public void deleteCategoryCascade(Category category) {

  }

 

  public List findProductByCategory(final Category category) {

    return null;

  }

}

10.4.3  TDD又來了:規劃測試案例
有了以上這套DAO僞實現之後,先不急着撰寫DAO的具體實現,回想一下第三章介紹的測試驅動開發(TDD),這裏不妨再次運用它。

首先是規劃測試,沿用上一章代碼9.32的測試案例並改寫,使其中的StockDao和JdbcTemplate接受IoC注射(這裏引入JdbcTemplate目的只是爲了方便測試),如代碼10.23所示。

代碼10.23  HibernateStockDaoTest.java

 

package chapter10.spring.hibernate;

 

import java.util.List;

 

import junit.framework.TestCase;

 

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import org.springframework.jdbc.core.JdbcTemplate;

 

import chapter10.hibernate.domain.Category;

import chapter10.hibernate.domain.Product;

 

public class HibernateStockDaoTest extends TestCase {

 

  private StockDao stockDao;

  private JdbcTemplate jdbcTemplate;

  private Product product1;

  private Product product2;

  private Category category;

 

  protected void setUp() throws Exception {

      ApplicationContext ctx = new ClassPathXmlApplicationContext(

       new String[]{"ch10/spring/hibernate/dataAccessContext-local.xml"});

 

    stockDao = (StockDao)ctx.getBean("stockDao");

    jdbcTemplate = (JdbcTemplate)ctx.getBean("jdbcTemplate");

 

    category = new Category("RABBIT");

    category.setName("Rabbit");

    category.setDescn("Desciption of Rabbit");

 

    product1 = new Product("RABBIT-01");

    product2 = new Product("RABBIT-02");

    product1.setName("WhiteRabbit");

    product1.setDescn("Description of WhiteRabbit");

    product2.setName("BlackRabbit");

    product2.setDescn("Description of BlackRabbit");

 

    category.addProducts(product1);

    category.addProducts(product2);

  }

 

  public void testCreateCategory() {

    stockDao.createCategoryCascade(category);

    assertEquals(3, getCategoryAndProductSizeManually());

  }

 

  public void testDeleteCategory() {

    if(getCategoryAndProductSizeManually()==0) {

      stockDao.createCategoryCascade(category);

    }

    stockDao.deleteCategoryCascade(category);

    assertEquals(0, getCategoryAndProductSizeManually());

  }

 

  public void testRetrieveProductByCategory() {

    if(getCategoryAndProductSizeManually()==0) {

      stockDao.createCategoryCascade(category);

    }

    List productList = stockDao.retrieveProductBy(category);

    assertEquals(2, productList.size());

  }

 

  private int getCategoryAndProductSizeManually() {

    int categorySize = jdbcTemplate.queryForList(

      "SELECT * FROM CATEGORY WHERE CATID='RABBIT'").size();

    int productSize = jdbcTemplate.queryForList(

        "SELECT * FROM PRODUCT " +

        "WHERE PRODUCTID='RABBIT-01' OR PRODUCTID='RABBIT-02'").size();

    return (categorySize + productSize);

  }

 

 

}

預期地,該測試案例運行後將全部失敗,這正是TDD第一步的效果,如圖10.2所示。

 

圖10.2  Spring結合Hibernate首次測試完敗

10.4.4  TDD又來了:完善基礎設施
檢查後發現,錯誤的原因是沒有在Spring配置文件中定義stockDao。

向代碼10.20中追加三個定義:stockDao、HibernateTemplate、jdbcTemplate,如下:

  <bean id="jdbcTemplate"

 class="org.springframework.jdbc.core.JdbcTemplate">

    <constructor-arg ref="dataSource"></constructor-arg>

  </bean>

  <bean id="hibernateTemplate"

  class="org.springframework.orm.hibernate3.HibernateTemplate">

    <constructor-arg ref="sessionFactory"/>

  </bean>

  <bean id="stockDao"

  class="chapter10.spring.hibernate.HibernateStockDao">

    <property name="template" ref="hibernateTemplate"/>

  </bean>

再次運行測試案例,可以看到,基礎設施已經搭建成功,現在依然存在的錯誤是未實現HibernateStockDao。

10.4.5  添加HibernateTemplate和HibernateCallback實現,交付測試
下面的任務就是使用HibernateTemplate和HibernateCallback來完成DAO的具體實現,如代碼10.24所示。

代碼10.24  HibernateStockDao.java

package chapter10.spring.hibernate;

 

import java.util.List;

 

import org.hibernate.HibernateException;

import org.hibernate.Session;

import org.springframework.orm.hibernate3.HibernateCallback;

import org.springframework.orm.hibernate3.HibernateTemplate;

 

import chapter10.hibernate.domain.Category;

 

public class HibernateStockDao implements StockDao {

 

  private HibernateTemplate template;

 

  public void setTemplate(HibernateTemplate template) {

    this.template = template;

  }

 

  public void createCategoryCascade(Category category) {

    template.save(category);

  }

 

  public void deleteCategoryCascade(Category category) {

    template.delete(category);

  }

 

/**

       * 使用HQL進行對象級別的查詢

       */

  public List retrieveProductBy(final Category category) {

    final String catid = category.getCatid();

    return (List)template.execute(new HibernateCallback() {

      public Object doInHibernate(Session session)

        throws HibernateException {

        String category = "chapter10.hibernate.domain.Category";

        List products =

          session.createQuery("select category.products from "+

              category+" category where category.catid='"+catid+"'").list();

        return products;

      }

    });

  }

}

HibernateTemplate提供了許多便利的方法,並且在代碼中不需要處理包括Session的打開關閉,事務的開啓,異常地捕捉等等操作。並且,該template也是線程安全的。通過HibernateCallback接口的回調,就可以和Hibernate的Session打交道了。

上例中,通過回調,可以在doInHibernate()方法內執行更多的處理,比如調用原生Sesssion上的方法。不必擔心,在所有處理結束以後,HibernateTemplate仍會進行確保正確的Session打開和關閉以及事務的自動參與。其實對於簡單的一步操作,如save、delete、load、find等,HibernateTemplate可以很好地替換這種繁瑣的回調方法。比如上例中的retrieveProductBy()方法又可以改寫如下:

  public List retrieveProductBy(final Category category) {

    String categoryAlias = "chapter10.hibernate.domain.Category";

    return template.find("select category.products from "+

        categoryAlias+" category where category.catid=?",

        category.getCatid());

  }

撰寫完畢,運行早已準備好的測試案例,效果如圖10.3所示。

 

圖10.3  Spring結合Hibernate案例測試通過

除了以上對HibernateTemplate的使用方法,Spring還提供了一個便利的HibernateDaoSupport超類,這和用以支持JDBC的JdbcDaoSupport類以及用以支持IBatis的SqlMapClientDaoSupport類概念是一致的,改動的方法也大相徑庭。

限於篇幅,這裏不再列出,在隨書源碼中可以找到運用這個支持類的實現,它們分別是chapter10.spring.hibernate..HibernateStockDao2和dataAccessContext-support-local.xml。

10.4.6  聲明式管理Hibernate本地事務
Spring提供了一種統一的IoC方式來管理Hibernate事務(本地或者分佈式事務)。從Spring接手hibernate.cfg.xml(Hibernate的基本配置文件)起,Hibernate事務便輕易交由Spring拖管了。

說明:在上一章介紹IBatis和DAO的時候,曾經針對事務和DAO的關係簡單的進行了探討。通常DAO的粒度應該都是比較細的,即它們只是一些單步的CRUD操作,所以就需要引入一個業務對象來包裹DAO,這樣,就可以在業務對象的基礎上,提供更粗粒度的事務劃分了(比如跨越多個DAO的方法調用進行事務管理)。

爲了能對DAO進行更粗粒度的事務控制,需要爲其增加一個業務對象。下面給出了該業務對象的接口和實現,如代碼10.25~10.26所示。

代碼10.25  StockFacade.java

 

package chapter10.spring.hibernate;

 

import chapter10.hibernate.domain.Category;

 

public interface StockFacade {

  public void business1(Category category);

  public void someOtherBusiness();

}

 

代碼10.26  BusinessFacadeImpl.java

 

public class BusinessFacadeImpl implements StockFacade {

  private StockDao stockDao;

  public void setStockDao(StockDao stockDao) {

    this.stockDao = stockDao;

  }

  public void business1(Category category) {

    stockDao.createCategoryCascade(category);

    stockDao.retrieveProductBy(category);

    stockDao.deleteCategoryCascade(category);

  }

  public void someOtherBusiness() {

    //other implemention

  }

}

接着給出關於事務策略的配置,其中使用了Spring針對Hibernate3給出的HibernateTransactionManager,它提供了Hibernate的本地事務管理策略,如代碼10.27所示。

代碼10.27  transaction-context.xml

 

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

"http://www.springframework.org/dtd/spring-beans.dtd">

 

<beans>

  <bean id="transactionManager"

class="org.springframework.orm.hibernate3.HibernateTransactionManager">①

    <property name="sessionFactory" >

      <ref bean="sessionFactory" />

    </property>

  </bean>

 

  <bean id="business"

class="chapter10.spring.hibernate.BusinessFacadeImpl">

    <property name="stockDao">

      <ref bean="stockDao"/>

    </property>

  </bean>

 

  <bean id="businessProxy"

    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

    <property name="transactionManager">

      <ref bean="transactionManager" />

    </property>

    <property name="target">

      <ref bean="business" />

    </property>

    <property name="transactionAttributes">

      <props>

        <!-- 運行在當前事務範圍內,如果當前沒有啓動事務,那麼創建一個新的事務-->

        <prop key="business*">PROPAGATION_REQUIRED</prop>

        <!-- 運行在當前事務範圍內,如果當前沒有啓動事務,那麼拋出異常-->

        <prop key="someOtherBusiness*">PROPAGATION_MANDATORY</prop>

      </props>

    </property>

  </bean>

 

</beans>

 

代碼10.28  HibernateTransactionUsageTest.java

 

package chapter10.spring.hibernate;

 

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import chapter10.hibernate.domain.Category;

import junit.framework.TestCase;

 

public class HibernateTransactionUsageTest extends TestCase {

  private StockFacade stockBusiness;

  protected void setUp() throws Exception {

    String path = "ch10/spring/hibernate/";

    ApplicationContext ctx = new ClassPathXmlApplicationContext(

         new String[]{path+"dataAccessContext-support-local.xml",

             path+"transaction-context.xml"});

    stockBusiness = (StockFacade)ctx.getBean("businessProxy");

  }

  public void testTransctionUsage() {

    Category category = new Category("RABBIT");

    category.setName("Rabbit");

    category.setDescn("Desciption of Rabbit");

    stockBusiness.business1(category);

  }

}

10.4.7  聲明式管理Hibernate分佈式事務
通過Spring,還可以很方便地切換至另一種事務管理策略。比如需要提供分佈式事務管理策略時,只要替換一下配置即可,如代碼10.29所示。

代碼10.29  appContext-jta.xml

<beans>

  <bean id="transactionManager"

class="org.springframework.transaction.jta.JtaTransactionManager">

    <property name="sessionFactory" >

      <ref bean="sessionFactory" />

    </property>

  </bean>

 

  <bean id="myDataSource1"

class="org.springframework.jndi.JndiObjectFactoryBean">

      <property name="jndiName">

          <value>java:comp/env/jdbc/myds1</value>

      </property>

  </bean>

  <bean id="myDataSource2"

class="org.springframework.jndi.JndiObjectFactoryBean">

      <property name="jndiName">

          <value>java:comp/env/jdbc/myds2</value>

      </property>

  </bean>

 

  <bean id="sessionFactory1"

class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

    <property name="dataSource" ref="myDataSource1"/>

    <property name="configLocations">

      <value>hibernate.cfg1.xml</value>

    </property>

  </bean>

  <bean id="sessionFactory2"

class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

    <property name="dataSource" ref="myDataSource2"/>

    <property name="configLocations">

      <value>hibernate.cfg2.xml</value>

    </property>

  </bean>

 

  <bean id="dao1"

  class="daopackage1.DaoImpl">

    <property name="sessionFactory" ref="sessionFactory"/>

  </bean>

  <bean id="dao2"

  class="daopackage2.DaoImp2">

    <property name="sessionFactory" ref="sessionFactory"/>

  </bean>

</beans>

 

  <bean id="business" class="businesspackage.BusinessFacadeImpl">

    <property name="dao1">

      <ref bean="dao1"/>

    </property>

    <property name="dao2">

      <ref bean="dao2"/>

    </property>

  </bean>

  <bean id="businessProxy"

class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

    <property name="transactionManager">

      <ref bean="transactionManager" />

    </property>

    <property name="target">

      <ref bean="business" />

    </property>

    <property name="transactionAttributes">

      <props>

        <prop key="business*">PROPAGATION_REQUIRED</prop>

        <prop key="someOtherBusiness*">PROPAGATION_MANDATORY</prop>

      </props>

    </property>

  </bean>

</beans>

可以看到,對於橫跨多個Hibernate SessionFacotry的分佈式事務,只需簡單地將JtaTransactionManager和LocalSessionFactoryBean的定義結合起來就可以了,其中每個DAO通過bean屬性得到各自的SessionFactory引用。

說明:如果所有底層數據源都是支持事務的容器,那麼只需要對一個業務對象應用JtaTransactionManager策略,該對象就可以橫跨多個DAO和多個Session Factory來劃分事務了。使用Spring的最大好處就是,可通過配置來聲明式地管理事務,無需對應用代碼作任何改動。
 

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