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的最大好處就是,可通過配置來聲明式地管理事務,無需對應用代碼作任何改動。