?
本文檔使用 PHP中文網(wǎng)手冊(cè) 發(fā)布
我們將首先從Hibernate3 (http://www.hibernate.org/)開始,通過講解Hibernate在Spring環(huán)境中的使用來闡述Spring框架對(duì)于O/R Mapping工具的整合方式。 本章節(jié)將涉及到許多細(xì)節(jié)問題,并向你展示各種不同的DAO實(shí)現(xiàn)方式和事務(wù)劃分。這其中的絕大多數(shù)模式能夠被Spring支持的其他O/R Mapping工具所使用。 這一章節(jié)的其他部分將為你講述其他的O/R Mapping技術(shù),并給出一些簡(jiǎn)短的例子。
注意:Spring2.5版本需要Hibernate3.1或更高版本,不再提供對(duì)Hibernate2.1與Hibernate3.0版本的支持。
典型的業(yè)務(wù)程序經(jīng)常會(huì)被重復(fù)的資源管理代碼搞得混亂。很多項(xiàng)目都試圖創(chuàng)建自己的方案來解決這個(gè)問題,有時(shí)會(huì)為了編程方便而犧牲恰當(dāng)?shù)腻e(cuò)誤處理。
對(duì)于恰當(dāng)?shù)馁Y源管理,Spring提倡一種矚目而又簡(jiǎn)潔的解決方案:使用模板化的IoC,諸如基礎(chǔ)構(gòu)建類、回調(diào)接口以及使用AOP攔截器。
基礎(chǔ)構(gòu)建類負(fù)責(zé)恰當(dāng)?shù)馁Y源處理,以及將特定的異常代碼轉(zhuǎn)換為unchecked exception體系。Spring引進(jìn)了DAO異常體系,可適用于任何數(shù)據(jù)訪問策略。
對(duì)于直接使用JDBC的情況,前面章節(jié)提到的 JdbcTemplate
類負(fù)責(zé)處理connection,并正確地把 SQLException
轉(zhuǎn)換為 DataAccessException
體系,包括將與數(shù)據(jù)庫相關(guān)的SQL錯(cuò)誤代碼變成有意義的異常類。
Spring同時(shí)通過它們各自的事務(wù)管理器支持JTA和JDBC事務(wù)。
Spring同樣也提供了對(duì)Hibernate和JDO的支持,包括 HibernateTemplate
/ JdoTemplate
類似于 JdbcTemplate
,
HibernateInterceptor
/ JdoInterceptor
以及一個(gè) Hibernate / JDO 事務(wù)管理器。
這樣做的主要目的是為了能夠清晰地劃分應(yīng)用程序?qū)哟味还苁褂煤畏N數(shù)據(jù)訪問和事務(wù)管理技術(shù),從而降低各個(gè)應(yīng)用程序?qū)ο笾g的耦合。
業(yè)務(wù)邏輯不再依賴于特定的數(shù)據(jù)訪問與事務(wù)策略;不再有硬編碼的資源查找、不再有難以替換的singletons、不再有用戶自定義的服務(wù)注冊(cè)。
Spring提供了一個(gè)簡(jiǎn)單且穩(wěn)固的方案使得各種應(yīng)用邏輯對(duì)象連接在一起,使這些對(duì)象可重用,并盡可能不依賴容器。
所有的數(shù)據(jù)訪問技術(shù)都能獨(dú)立使用,但是他們?cè)赟pring提供的基于XML配置且無需依賴Spring的普通JavaBean下會(huì)與application Context整合的更好。
在典型的Spring應(yīng)用程序中,很多重要的對(duì)象都是JavaBeans:數(shù)據(jù)訪問template、數(shù)據(jù)訪問對(duì)象(使用template)、事務(wù)管理器、業(yè)務(wù)邏輯對(duì)象(使用數(shù)據(jù)訪問對(duì)象和事務(wù)管理器)、web視圖解析器、web控制器(使用業(yè)務(wù)服務(wù))等等。
為了避免硬編碼的資源查找與應(yīng)用程序?qū)ο缶o密耦合,Spring允許你在Spring容器中以bean的方式定義諸如JDBC DataSource
或者Hibernate SessionFactory
的數(shù)據(jù)訪問資源。
任何需要進(jìn)行資源訪問的應(yīng)用程序?qū)ο笾恍枰钟羞@些事先定義好的實(shí)例的引用(DAO定義在下一章節(jié)介紹),下面的代碼演示如何創(chuàng)建一個(gè)JDBC DataSource
和Hibernate SessionFactory
<beans> <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="org.hsqldb.jdbcDriver"/> <property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/> <property name="username" value="sa"/> <property name="password" value=""/> </bean> <bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="myDataSource"/> <property name="mappingResources"> <list> <value>product.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.HSQLDialect </value> </property> </bean> </beans>
將一個(gè)本地定義的,如Jakarta Commons DBCP的 BasicDataSource
切換為一個(gè)JNDI定位的DataSource(通常由應(yīng)用程序服務(wù)器管理),僅僅需要改變配置:
<beans> <bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="java:comp/env/jdbc/myds"/> </bean> </beans>
通過使用Spring的 JndiObjectFactoryBean
來暴露和獲,你也可以訪問一個(gè)JNDI定位的Hibernate SessionFactory
。
當(dāng)然,如果在EJB上下文之外,這是不必要的。
對(duì)于特定的數(shù)據(jù)訪問對(duì)象或業(yè)務(wù)對(duì)象的方法,基本的模板編程模型看起來像下面所示的代碼那樣。
對(duì)于這些外部對(duì)象來說,沒有任何實(shí)現(xiàn)特定接口的約束,僅僅要求提供一個(gè)Hibernate SessionFactory
。
它可以從任何地方得到,不過比較適宜的方法是從Spring的IoC容器中取得bean的引用:通過簡(jiǎn)單的 setSessionFactory(..)
這個(gè)bean的setter方法。
下面的代碼片段展示了在Spring容器中一個(gè)DAO的定義,它引用了上面定義的 SessionFactory
,同時(shí)展示了一個(gè)DAO方法的具體實(shí)現(xiàn)。
<beans> <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="sessionFactory" ref="mySessionFactory"/> </bean> </beans>
public class ProductDaoImpl implements ProductDao { private HibernateTemplate hibernateTemplate; public void setSessionFactory(SessionFactory sessionFactory) { this.hibernateTemplate = new HibernateTemplate(sessionFactory); } public Collection loadProductsByCategory(String category) throws DataAccessException { return this.hibernateTemplate.find("from test.Product product where product.category=?", category); } }
除了很多類似上例中易用的方法外,HibernateTemplate
類提供了大量方法對(duì)應(yīng)Hibernate Session
接口中暴露的方法。當(dāng)你需要使用的Session
方法沒有在HibernateTemplate
中提供時(shí),可以通過下面提供的基于回調(diào)的方案來實(shí)現(xiàn)。
public class ProductDaoImpl implements ProductDao { private HibernateTemplate hibernateTemplate; public void setSessionFactory(SessionFactory sessionFactory) { this.hibernateTemplate = new HibernateTemplate(sessionFactory); } public Collection loadProductsByCategory(final String category) throws DataAccessException { return this.hibernateTemplate.execute(new HibernateCallback() { public Object doInHibernate(Session session) { Criteria criteria = session.createCriteria(Product.class); criteria.add(Expression.eq("category", category)); criteria.setMaxResults(6); return criteria.list(); } }; } }
一個(gè)回調(diào)實(shí)現(xiàn)能夠有效地在任何Hibernate數(shù)據(jù)訪問中使用。HibernateTemplate
會(huì)確保當(dāng)前Hibernate的 Session
實(shí)例的正確打開和關(guān)閉,并直接參與到事務(wù)管理中去。
Template實(shí)例不僅是線程安全的,同時(shí)它也是可重用的。因而他們可以作為外部對(duì)象的實(shí)例變量而被持有。對(duì)于那些簡(jiǎn)單的諸如find、load、saveOrUpdate或者delete操作的調(diào)用,HibernateTemplate
提供可選擇的快捷函數(shù)來替換這種回調(diào)的實(shí)現(xiàn)。
不僅如此,Spring還提供了一個(gè)簡(jiǎn)便的 HibernateDaoSupport
基類,這個(gè)類提供了 setSessionFactory(..)
方法來接受一個(gè) SessionFactory
對(duì)象,同時(shí)提供了 getSessionFactory()
和 getHibernateTemplate()
方法給子類使用。
綜合了這些,對(duì)于那些典型的業(yè)務(wù)需求,就有了一個(gè)非常簡(jiǎn)單的DAO實(shí)現(xiàn):
public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao { public Collection loadProductsByCategory(String category) throws DataAccessException { return this.getHibernateTemplate().find( "from test.Product product where product.category=?", category); } }
作為不使用Spring的 HibernateTemplate
來實(shí)現(xiàn)DAO的替代解決方案,你依然可以用傳統(tǒng)的編程風(fēng)格來編寫你的數(shù)據(jù)訪問代碼。
無需將你的Hibernate訪問代碼包裝在一個(gè)回調(diào)中,只需符合Spring的通用的 DataAccessException
異常體系。
HibernateDaoSupport
基類提供了訪問與當(dāng)前事務(wù)綁定的 Session
對(duì)象的函數(shù),因而能保證在這種情況下異常的正確轉(zhuǎn)化。
類似的函數(shù)同樣可以在 SessionFactoryUtils
類中找到,但他們以靜態(tài)方法的形式出現(xiàn)。
值得注意的是,通常將 'false
' 作為參數(shù)值(表示是否允許創(chuàng)建)傳遞給 getSession(..)
方法進(jìn)行調(diào)用。
此時(shí),整個(gè)調(diào)用將在同一個(gè)事務(wù)內(nèi)完成(它的整個(gè)生命周期由事務(wù)控制,避免了關(guān)閉返回的 Session
的需要)。
public class HibernateProductDao extends HibernateDaoSupport implements ProductDao { public Collection loadProductsByCategory(String category) throws DataAccessException, MyException { Session session = getSession(false); try { Query query = session.createQuery("from test.Product product where product.category=?"); query.setString(0, category); List result = query.list(); if (result == null) { throw new MyException("No search results."); } return result; } catch (HibernateException ex) { throw convertHibernateAccessException(ex); } } }
這種直接使用Hibernate訪問代碼的好處在于它允許你在數(shù)據(jù)訪問代碼中拋出 任何 checked exception,而 HibernateTemplate
卻受限于回調(diào)中的unchecked exception。
注意,你通??梢詫⑦@些應(yīng)用程序的異常處理推遲到回調(diào)函數(shù)之后,這樣,你依然可以正常使用 HibernateTemplate
。
一般來說,HibernateTemplate
類所提供的許多方法在許多情況下看上去更簡(jiǎn)單和便捷。
Hibernate3 引入了一個(gè)新的特性:“帶上下文環(huán)境的Session”。 這一特性使得Hibernate自身具備了每個(gè)事務(wù)綁定當(dāng)前 Session
對(duì)象的功能。
這與Spring中每個(gè)Hibernate的 Session
與事務(wù)同步的功能大致相同。一個(gè)相應(yīng)的基于原生的Hibernate API的DAO實(shí)現(xiàn)正如下例所示:
public class ProductDaoImpl implements ProductDao { private SessionFactory sessionFactory; public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } public Collection loadProductsByCategory(String category) { return this.sessionFactory.getCurrentSession() .createQuery("from test.Product product where product.category=?") .setParameter(0, category) .list(); } }
這種風(fēng)格與你在Hibernate文檔和示例中見到的非常類似,除了DAO實(shí)現(xiàn)類中持有了一個(gè) SessionFactory
的實(shí)例變量。
我們強(qiáng)烈推薦這種基于實(shí)例變量的DAO構(gòu)建方式,而不是使用那種過去由Hibernate的示例程序中提到的 靜態(tài)的
HibernateUtil
類。
(通常來說,不要在靜態(tài)變量中保存任何資源信息除非 確實(shí) 有這個(gè)必要)。
上述DAO遵循依賴注入模式:它如同使用Spring的 HibernateTemplate
進(jìn)行編程那樣,適合在Spring IoC容器中進(jìn)行配置。
當(dāng)然,這樣的DAO還可以建立在一個(gè)普通的Java類中(諸如在Unit Test中): 僅僅需要初始化這個(gè)類,
調(diào)用 setSessionFactory(..)
方法設(shè)置你所期望的工廠資源。 以Spring的bean的定義方式,它看上去就像這樣:
<beans> <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="sessionFactory" ref="mySessionFactory"/> </bean> </beans>
這種DAO訪問方式的主要優(yōu)勢(shì)在于它僅僅依賴于Hibernate API本身而無需引入任何Spring的類。 從無入侵性的角度來看,這一點(diǎn)非常吸引人。對(duì)于Hibernate開發(fā)人員來說也無疑更加自然。
然而,這樣的DAO訪問方式會(huì)拋出原生 HibernateException
(這是一個(gè)無需聲明或捕獲的unchecked exception),
這意味著,DAO的調(diào)用者只能以致命的錯(cuò)誤來處理這些異常,除非完全依賴Hibernate自身的異常體系。
因而,除非你將DAO的調(diào)用者綁定到具體的實(shí)現(xiàn)策略上去,否則你將無法捕獲特定的異常原因,諸如樂觀鎖異常。
這種折中平衡或許可以被接受,如果你的應(yīng)用完全基于Hibernate或者無需進(jìn)行特殊的異常處理。
幸運(yùn)的是,Spring的 LocalSessionFactoryBean
可以在任何Spring的事務(wù)管理策略下,
提供對(duì)Hibernate的 SessionFactory.getCurrentSession()
方法的支持,它將返回當(dāng)前受Spring事務(wù)管理(甚至是 HibernateTransactionManager
管理的)的 Session
對(duì)象。
當(dāng)然,該函數(shù)的標(biāo)準(zhǔn)行為依然有效:返回當(dāng)前與正在進(jìn)行的JTA事務(wù)(無論是Spring的 JtaTransactionManager
、EJB CMT或者普通的JTA)綁定的 Session
對(duì)象。
總體來說,DAO可以基于Hibernate3的原生API實(shí)現(xiàn),同時(shí),它依舊需要能夠參與到Spring的事務(wù)管理中。
我們可以在這些低層次的數(shù)據(jù)訪問服務(wù)之上的應(yīng)用程序進(jìn)行更高層次的事務(wù)劃分,從而讓事務(wù)能夠橫跨多個(gè)操作。
這里對(duì)于相關(guān)的業(yè)務(wù)邏輯對(duì)象同樣沒有實(shí)現(xiàn)接口的限制,它只需要一個(gè)Spring的 PlatformTransactionManager
。
同SessionFactory一樣,它可以來自任何地方,但是最好是一個(gè)經(jīng)由 setTransactionManager(..)
方法注入的bean的引用,正如使用 setProductDao
方法來設(shè)置 productDAO
一樣。
下面演示了在Spring的application context中定義一個(gè)事務(wù)管理器和一個(gè)業(yè)務(wù)對(duì)象,以及具體的業(yè)務(wù)方法實(shí)現(xiàn):
<beans> <bean id="myTxManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="mySessionFactory"/> </bean> <bean id="myProductService" class="product.ProductServiceImpl"> <property name="transactionManager" ref="myTxManager"/> <property name="productDao" ref="myProductDao"/> </bean> </beans>
public class ProductServiceImpl implements ProductService {
private TransactionTemplate transactionTemplate;
private ProductDao productDao;
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public void setProductDao(ProductDao productDao) {
this.productDao = productDao;
}
public void increasePriceOfAllProductsInCategory(final String category) {
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
List productsToChange = this.productDao.loadProductsByCategory(category);
// do the price increase...
}
}
);
}
}
作為可選方案,我們可以使用Spring聲明式的事務(wù)支持。聲明式的事務(wù)支持通過配置Spring容器中的AOP Transaction Interceptor來替換事務(wù)劃分的硬編碼。 這將使你可以從每個(gè)業(yè)務(wù)方法中重復(fù)的事務(wù)劃分代碼中解放出來,真正專注于為你的應(yīng)用添加有價(jià)值的業(yè)務(wù)邏輯代碼。此外,類似傳播行為和隔離級(jí)別等事務(wù)語義能夠在配置文件中改變,而不會(huì)影響具體的業(yè)務(wù)對(duì)象實(shí)現(xiàn)。
<beans>
<bean id="myTxManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
<bean id="myProductService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="product.ProductService"/>
<property name="target">
<bean class="product.DefaultProductService">
<property name="productDao" ref="myProductDao"/>
</bean>
</property>
<property name="interceptorNames">
<list>
<value>myTxInterceptor</value> <!-- the transaction interceptor (configured elsewhere) -->
</list>
</property>
</bean>
</beans>
public class ProductServiceImpl implements ProductService { private ProductDao productDao; public void setProductDao(ProductDao productDao) { this.productDao = productDao; } // notice the absence of transaction demarcation code in this method // Spring's declarative transaction infrastructure will be demarcating transactions on your behalf public void increasePriceOfAllProductsInCategory(final String category) { List productsToChange = this.productDao.loadProductsByCategory(category); // ... } }
Spring的 TransactionInterceptor
允許任何應(yīng)用程序的checked exception在回調(diào)代碼中拋出,而 TransactionTemplate
在回調(diào)中則嚴(yán)格要求被封裝成unchecked exception。
TransactionTemplate
會(huì)在一個(gè)unchecked exception拋出時(shí),或者當(dāng)事務(wù)被應(yīng)用程序通過 TransactionStatus
標(biāo)記為rollback-only時(shí)觸發(fā)一個(gè)數(shù)據(jù)庫回滾操作。
TransactionInterceptor
默認(rèn)進(jìn)行同樣的操作,但是它允許對(duì)每個(gè)方法配置自己的rollback策略。
下面列出的高級(jí)別的聲明式的事務(wù)管理方案并沒有使用 ProxyFactoryBean
,它將使那些大量的業(yè)務(wù)對(duì)象需要被納入到事務(wù)管理中時(shí)的配置變得非常簡(jiǎn)單。
在你打算繼續(xù)閱讀下一部分之前,我們 強(qiáng)烈 推薦你想去閱讀 第?9.5?節(jié) “聲明式事務(wù)管理” 這一章節(jié)。
<?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: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-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!-- SessionFactory
, DataSource
, etc. omitted -->
<bean id="myTxManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
<aop:config>
<aop:pointcut id="productServiceMethods" expression="execution(* product.ProductService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="myTxManager">
<tx:attributes>
<tx:method name="increasePrice*" propagation="REQUIRED"/>
<tx:method name="someOtherBusinessMethod" propagation="REQUIRES_NEW"/>
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<bean id="myProductService" class="product.SimpleProductService">
<property name="productDao" ref="myProductDao"/>
</bean>
</beans>
TransactionTemplate
和 TransactionInterceptor
都將真正的事務(wù)處理委托給一個(gè) PlatformTransactionManager
實(shí)例來處理。
在Hibernate應(yīng)用中,它可以是一個(gè) HibernateTransactionManager
(對(duì)于單獨(dú)一個(gè)的Hibernate SessionFactory
使用一個(gè) ThreadLocal
的 Session
)或一個(gè) JtaTransactionManager
(代理給容器的JTA子系統(tǒng))。
你甚至可以使用自定義的 PlatformTransactionManager
的實(shí)現(xiàn)。因而,在你的應(yīng)用碰到了特定的分布式事務(wù)的部署需求時(shí)(類似將原來的Hibernate事務(wù)管理轉(zhuǎn)變?yōu)镴TA),僅僅需要改變配置而已:簡(jiǎn)單將Hibernate的事務(wù)管理器替換成JTA事務(wù)實(shí)現(xiàn)。
任何的事務(wù)劃分和數(shù)據(jù)訪問的代碼都無需因此而改變,因?yàn)樗麄儍H僅使用了通用的事務(wù)管理API。
對(duì)于橫跨多個(gè)Hibernate SessionFacotry的分布式事務(wù),只需簡(jiǎn)單地將 JtaTransactionManager
同多個(gè) LocalSessionFactoryBean
的定義結(jié)合起來作為事務(wù)策略。
你的每一個(gè)DAO通過bean屬性得到各自的 SessionFactory
引用。如果所有的底層JDBC數(shù)據(jù)源都是支持事務(wù)的容器,那么只要業(yè)務(wù)對(duì)象使用了 JtaTransactionManager
作為事務(wù)策略,它就可以橫跨多個(gè)DAO和多個(gè)session factories來劃分事務(wù),而不需要做任何特殊處理。
<beans> <bean id="myDataSource1" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName value="java:comp/env/jdbc/myds1"/> </bean> <bean id="myDataSource2" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="java:comp/env/jdbc/myds2"/> </bean> <bean id="mySessionFactory1" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="myDataSource1"/> <property name="mappingResources"> <list> <value>product.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.MySQLDialect hibernate.show_sql=true </value> </property> </bean> <bean id="mySessionFactory2" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="myDataSource2"/> <property name="mappingResources"> <list> <value>inventory.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.OracleDialect </value> </property> </bean> <bean id="myTxManager" class="org.springframework.transaction.jta.JtaTransactionManager"/> <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="sessionFactory" ref="mySessionFactory1"/> </bean> <bean id="myInventoryDao" class="product.InventoryDaoImpl"> <property name="sessionFactory" ref="mySessionFactory2"/> </bean> <!-- this shows the Spring 1.x style of declarative transaction configuration --> <!-- it is totally supported, 100% legal in Spring 2.x, but see also above for the sleeker, Spring 2.0 style --> <bean id="myProductService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="myTxManager"/> <property name="target"> <bean class="product.ProductServiceImpl"> <property name="productDao" ref="myProductDao"/> <property name="inventoryDao" ref="myInventoryDao"/> </bean> </property> <property name="transactionAttributes"> <props> <prop key="increasePrice*">PROPAGATION_REQUIRED</prop> <prop key="someOtherBusinessMethod">PROPAGATION_REQUIRES_NEW</prop> <prop key="*">PROPAGATION_SUPPORTS,readOnly</prop> </props> </property> </bean> </beans>
HibernateTransactionManager
和 JtaTransactionManager
都允許恰當(dāng)?shù)腏VM級(jí)別的Hibernate緩存處理,而無需使用與容器相關(guān)的事務(wù)管理器或JCA連接器。(只要不是由EJB發(fā)起的事務(wù))
對(duì)于特定的 DataSource
,HibernateTransactionManager
能夠?yàn)槠胀ǖ腏DBC訪問代碼提供Hibernate所使用的 Connection
。
這一功能允許你可以完全混和使用Hibernate/JDBC進(jìn)行數(shù)據(jù)訪問而無需依賴JTA在高層次代碼中進(jìn)行事務(wù)劃分,只要你依然訪問的是同一個(gè)數(shù)據(jù)庫!
HibernateTransactionManager
能夠自動(dòng)地將Hibernate事務(wù)暴露為JDBC事務(wù),如果你通過設(shè)置 DataSource
來建立SessionFactory
對(duì)象(通過設(shè)置 LocalSessionFactoryBean
中的“dataSource”屬性)。
另外一種配置方法是通過設(shè)置 HibernateTransactionManager
的“dataSource”屬性,明確指定事務(wù)需要暴露的 DataSource
。
Spring的資源管理允許你簡(jiǎn)單地在一個(gè)JNDI的 SessionFactory
和一個(gè)本地的 SessionFactory
之間切換而無需更改任何一行應(yīng)用程序代碼。
把資源定義放在容器中還是放在應(yīng)用程序本地中主要是由使用的事務(wù)策略決定的。與Spring定義的本地 SessionFactory
相比,一個(gè)手工注冊(cè)的JNDI SessionFactory
并沒有體現(xiàn)出多大的優(yōu)勢(shì)。
通過Hibernate的JCA連接器來部署一個(gè) SessionFactory
提供了能使之參與到J2EE服務(wù)器管理架構(gòu)的增值服務(wù),不過除此之外也并沒有增加實(shí)際的價(jià)值。
Spring對(duì)事務(wù)管理的支持有一個(gè)非常重要的好處:它不依賴于任何容器。使用非JTA的任何其他事務(wù)策略的配置,程序也能在獨(dú)立的測(cè)試環(huán)境下正常工作。
尤其對(duì)于那些典型的單數(shù)據(jù)庫事務(wù)情況下,這將是一個(gè)非常輕量級(jí)而又強(qiáng)大的JTA替代方案。當(dāng)你使用一個(gè)本地的EJB SLSB來驅(qū)動(dòng)事務(wù)時(shí),即時(shí)你可能只訪問一個(gè)數(shù)據(jù)庫而且僅僅通過CMT使用SLSB的聲明性事務(wù),你仍然要依賴于EJB容器和JTA。
使用編程式JTA替換方案依然需要J2EE環(huán)境,JTA不僅對(duì)于JTA本身,還對(duì)于JNDI的 DataSource
實(shí)例引入了容器依賴。
對(duì)于非Spring環(huán)境的JTA驅(qū)動(dòng)的Hibernate事務(wù),你必須使用Hibernate JCA連接器或者額外的Hibernate事務(wù)代碼及 TransactionManagerLookup
配置,來恰當(dāng)?shù)靥幚鞪VM級(jí)別的緩存。
Spring驅(qū)動(dòng)的事務(wù)管理與本地定義的 SessionFactory
能夠工作得非常好,就像使用本地的JDBC DataSource
一樣,當(dāng)然前提必須是訪問單個(gè)數(shù)據(jù)庫。
因此當(dāng)你真正面對(duì)分布式事務(wù)的需求時(shí),可以馬上回到Spring的JTA事務(wù)。必須要注意,一個(gè)JCA連接器需要特定容器的部署步驟,而且首先容器要支持JCA。
這要比使用本地資源定義和Spring驅(qū)動(dòng)事務(wù)來部署一個(gè)簡(jiǎn)單的Web應(yīng)用麻煩得多。而且你通常需要特定企業(yè)版本的容器,但是像類似WebLogic的Express版本并不提供JCA。
一個(gè)僅使用本地資源并且針對(duì)一個(gè)數(shù)據(jù)庫的事務(wù)操作的Spring應(yīng)用將可以在任何一種J2EE的Web容器中工作(不需要JTA、JCA或者EJB),比如Tomcat、Resin甚至普通的Jetty。
除此之外,這樣的中間層可以在桌面應(yīng)用或測(cè)試用例中被簡(jiǎn)單地重用。
考慮一下所有的情況:如果你不使用EJB,堅(jiān)持使用本地 SessionFactory
以及Spring的 HibernateTransactionManager
或者 JtaTransactionManager
,
你將獲得包括適當(dāng)?shù)腏VM級(jí)別上的緩存以及分布式事務(wù)在內(nèi)的所有好處,而不會(huì)有任何容器部署的麻煩。通過JCA連接器的Hibernate的 SessionFactory
的JNDI注冊(cè)僅僅在EJB中使用會(huì)帶來好處。
在一些具有非常嚴(yán)格的 XADataSource
實(shí)現(xiàn)的JTA環(huán)境中(目前來說只是WebLogic和WebSphere的某些版本)當(dāng)你將Hibernate配置成不感知JTA PlatformTransactionManager
對(duì)象時(shí),容器可能會(huì)在應(yīng)用服務(wù)器日志中報(bào)出一些警告和異常。
這些警告和異常通常會(huì)說:“正在被訪問的連接不再有效,或者JDBC連接不再有效,可能由于事務(wù)不再處于激活狀態(tài)”。下面是一個(gè)從WebLogic上拋出的異常:
java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No further JDBC access is allowed within this transaction.
這樣的錯(cuò)誤警告很容易解決:只要簡(jiǎn)單的把Hibernate配置成感知到JTA PlatformTransactionManager
實(shí)例的存在即可,同時(shí)將他們進(jìn)行同步(與Spring同步)??梢杂袃煞N方法達(dá)到這種效果:
如果你已經(jīng)在application context中定義并獲取了JTA PlatformTransactionManager
對(duì)象(或許來自通過 JndiObjectFactoryBean
得到的JNDI)并已經(jīng)將它注入到類似Spring的 JtaTransactionManager
中,
那么最簡(jiǎn)單的方式就是指定這個(gè)bean的引用作為 LocalSessionFactoryBean
的 jtaTransactionManager 屬性。Spring將使這個(gè)對(duì)象被Hibernate所感知。
多數(shù)情況下,你還沒有得到JTA的 PlatformTransactionManager
實(shí)例(由于Spring的 JtaTransactionManager
能夠自己找到它),所以你需要自行配置Hibernate并直接尋找資源。
正如Hibernate文檔中提到的,這可以通過在Hibernate配置一個(gè)應(yīng)用服務(wù)器特定的 TransactionManagerLookup
類來完成。
對(duì)于恰當(dāng)?shù)氖褂梅椒?,你無需了解更多的東西。但是我們?cè)谶@里將描述一下,對(duì)于將Hibernate配置為感知或者不感知JTA的 PlatformTransactionManager
對(duì)象這兩種情況下,整個(gè)事務(wù)的執(zhí)行順序。
Hibernate被配置成不感知JTA PlatformTransactionManager
的情況下,當(dāng)一個(gè)JTA事務(wù)提交時(shí),整個(gè)事件的執(zhí)行順序如下:
JTA 事務(wù)提交
Spring的 JtaTransactionManager
同步到JTA事務(wù),它被JTA事務(wù)管理器通過調(diào)用 afterCompletion 執(zhí)行回調(diào)。
在所有其他活動(dòng)中,這將由Spring向Hibernate觸發(fā)一個(gè)回調(diào),通過Hibernate的 afterTransactionCompletion
回調(diào)(用于清除Hibernate緩存),然后緊跟著一個(gè)明確的Hibernate Session的 close()
調(diào)用。
這將使Hibernate試圖去關(guān)閉JDBC的Connection。
在某些環(huán)境中,Connection.close()
的調(diào)用將會(huì)觸發(fā)一個(gè)警告或者錯(cuò)誤信息。
這是由于特定的應(yīng)用服務(wù)器將不再考慮 Connection
的可用性,因?yàn)榇藭r(shí)事務(wù)已經(jīng)被提交了。
在Hibernate被配置成感知JTA的 PlatformTransactionManager
的情況下,當(dāng)一個(gè)JTA事務(wù)提交時(shí),整個(gè)事件的執(zhí)行順序如下:
JTA 事務(wù)準(zhǔn)備提交
Spring的 JtaTransactionManager
同步到JTA事務(wù),因而它被JTA事務(wù)管理器通過調(diào)用 beforeCompletion 執(zhí)行回調(diào)。
Spring能感知到Hibernate自身被同步到JTA Transaction,因而表現(xiàn)得與先前那種情況有所不同。
假設(shè)Hibernate的 Session
需要被關(guān)閉,Spring將負(fù)責(zé)關(guān)閉它。
JTA 事務(wù)提交
Hibernate將與JTA transaction進(jìn)行同步,因而它被JTA事務(wù)管理器通過調(diào)用 afterCompletion 執(zhí)行回調(diào),并且適時(shí)地清除緩存。