?
Dokumen ini menggunakan Manual laman web PHP Cina Lepaskan
我們將首先從Hibernate3 (http://www.hibernate.org/)開始,通過講解Hibernate在Spring環(huán)境中的使用來闡述Spring框架對于O/R Mapping工具的整合方式。 本章節(jié)將涉及到許多細節(jié)問題,并向你展示各種不同的DAO實現(xiàn)方式和事務(wù)劃分。這其中的絕大多數(shù)模式能夠被Spring支持的其他O/R Mapping工具所使用。 這一章節(jié)的其他部分將為你講述其他的O/R Mapping技術(shù),并給出一些簡短的例子。
注意:Spring2.5版本需要Hibernate3.1或更高版本,不再提供對Hibernate2.1與Hibernate3.0版本的支持。
典型的業(yè)務(wù)程序經(jīng)常會被重復(fù)的資源管理代碼搞得混亂。很多項目都試圖創(chuàng)建自己的方案來解決這個問題,有時會為了編程方便而犧牲恰當(dāng)?shù)腻e誤處理。
對于恰當(dāng)?shù)馁Y源管理,Spring提倡一種矚目而又簡潔的解決方案:使用模板化的IoC,諸如基礎(chǔ)構(gòu)建類、回調(diào)接口以及使用AOP攔截器。
基礎(chǔ)構(gòu)建類負責(zé)恰當(dāng)?shù)馁Y源處理,以及將特定的異常代碼轉(zhuǎn)換為unchecked exception體系。Spring引進了DAO異常體系,可適用于任何數(shù)據(jù)訪問策略。
對于直接使用JDBC的情況,前面章節(jié)提到的 JdbcTemplate
類負責(zé)處理connection,并正確地把 SQLException
轉(zhuǎn)換為 DataAccessException
體系,包括將與數(shù)據(jù)庫相關(guān)的SQL錯誤代碼變成有意義的異常類。
Spring同時通過它們各自的事務(wù)管理器支持JTA和JDBC事務(wù)。
Spring同樣也提供了對Hibernate和JDO的支持,包括 HibernateTemplate
/ JdoTemplate
類似于 JdbcTemplate
,
HibernateInterceptor
/ JdoInterceptor
以及一個 Hibernate / JDO 事務(wù)管理器。
這樣做的主要目的是為了能夠清晰地劃分應(yīng)用程序?qū)哟味还苁褂煤畏N數(shù)據(jù)訪問和事務(wù)管理技術(shù),從而降低各個應(yīng)用程序?qū)ο笾g的耦合。
業(yè)務(wù)邏輯不再依賴于特定的數(shù)據(jù)訪問與事務(wù)策略;不再有硬編碼的資源查找、不再有難以替換的singletons、不再有用戶自定義的服務(wù)注冊。
Spring提供了一個簡單且穩(wěn)固的方案使得各種應(yīng)用邏輯對象連接在一起,使這些對象可重用,并盡可能不依賴容器。
所有的數(shù)據(jù)訪問技術(shù)都能獨立使用,但是他們在Spring提供的基于XML配置且無需依賴Spring的普通JavaBean下會與application Context整合的更好。
在典型的Spring應(yīng)用程序中,很多重要的對象都是JavaBeans:數(shù)據(jù)訪問template、數(shù)據(jù)訪問對象(使用template)、事務(wù)管理器、業(yè)務(wù)邏輯對象(使用數(shù)據(jù)訪問對象和事務(wù)管理器)、web視圖解析器、web控制器(使用業(yè)務(wù)服務(wù))等等。
為了避免硬編碼的資源查找與應(yīng)用程序?qū)ο缶o密耦合,Spring允許你在Spring容器中以bean的方式定義諸如JDBC DataSource
或者Hibernate SessionFactory
的數(shù)據(jù)訪問資源。
任何需要進行資源訪問的應(yīng)用程序?qū)ο笾恍枰钟羞@些事先定義好的實例的引用(DAO定義在下一章節(jié)介紹),下面的代碼演示如何創(chuàng)建一個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>
將一個本地定義的,如Jakarta Commons DBCP的 BasicDataSource
切換為一個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
來暴露和獲,你也可以訪問一個JNDI定位的Hibernate SessionFactory
。
當(dāng)然,如果在EJB上下文之外,這是不必要的。
對于特定的數(shù)據(jù)訪問對象或業(yè)務(wù)對象的方法,基本的模板編程模型看起來像下面所示的代碼那樣。
對于這些外部對象來說,沒有任何實現(xiàn)特定接口的約束,僅僅要求提供一個Hibernate SessionFactory
。
它可以從任何地方得到,不過比較適宜的方法是從Spring的IoC容器中取得bean的引用:通過簡單的 setSessionFactory(..)
這個bean的setter方法。
下面的代碼片段展示了在Spring容器中一個DAO的定義,它引用了上面定義的 SessionFactory
,同時展示了一個DAO方法的具體實現(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
類提供了大量方法對應(yīng)Hibernate Session
接口中暴露的方法。當(dāng)你需要使用的Session
方法沒有在HibernateTemplate
中提供時,可以通過下面提供的基于回調(diào)的方案來實現(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(); } }; } }
一個回調(diào)實現(xiàn)能夠有效地在任何Hibernate數(shù)據(jù)訪問中使用。HibernateTemplate
會確保當(dāng)前Hibernate的 Session
實例的正確打開和關(guān)閉,并直接參與到事務(wù)管理中去。
Template實例不僅是線程安全的,同時它也是可重用的。因而他們可以作為外部對象的實例變量而被持有。對于那些簡單的諸如find、load、saveOrUpdate或者delete操作的調(diào)用,HibernateTemplate
提供可選擇的快捷函數(shù)來替換這種回調(diào)的實現(xiàn)。
不僅如此,Spring還提供了一個簡便的 HibernateDaoSupport
基類,這個類提供了 setSessionFactory(..)
方法來接受一個 SessionFactory
對象,同時提供了 getSessionFactory()
和 getHibernateTemplate()
方法給子類使用。
綜合了這些,對于那些典型的業(yè)務(wù)需求,就有了一個非常簡單的DAO實現(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
來實現(xiàn)DAO的替代解決方案,你依然可以用傳統(tǒng)的編程風(fēng)格來編寫你的數(shù)據(jù)訪問代碼。
無需將你的Hibernate訪問代碼包裝在一個回調(diào)中,只需符合Spring的通用的 DataAccessException
異常體系。
HibernateDaoSupport
基類提供了訪問與當(dāng)前事務(wù)綁定的 Session
對象的函數(shù),因而能保證在這種情況下異常的正確轉(zhuǎn)化。
類似的函數(shù)同樣可以在 SessionFactoryUtils
類中找到,但他們以靜態(tài)方法的形式出現(xiàn)。
值得注意的是,通常將 'false
' 作為參數(shù)值(表示是否允許創(chuàng)建)傳遞給 getSession(..)
方法進行調(diào)用。
此時,整個調(diào)用將在同一個事務(wù)內(nèi)完成(它的整個生命周期由事務(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
類所提供的許多方法在許多情況下看上去更簡單和便捷。
Hibernate3 引入了一個新的特性:“帶上下文環(huán)境的Session”。 這一特性使得Hibernate自身具備了每個事務(wù)綁定當(dāng)前 Session
對象的功能。
這與Spring中每個Hibernate的 Session
與事務(wù)同步的功能大致相同。一個相應(yīng)的基于原生的Hibernate API的DAO實現(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實現(xiàn)類中持有了一個 SessionFactory
的實例變量。
我們強烈推薦這種基于實例變量的DAO構(gòu)建方式,而不是使用那種過去由Hibernate的示例程序中提到的 靜態(tài)的
HibernateUtil
類。
(通常來說,不要在靜態(tài)變量中保存任何資源信息除非 確實 有這個必要)。
上述DAO遵循依賴注入模式:它如同使用Spring的 HibernateTemplate
進行編程那樣,適合在Spring IoC容器中進行配置。
當(dāng)然,這樣的DAO還可以建立在一個普通的Java類中(諸如在Unit Test中): 僅僅需要初始化這個類,
調(diào)用 setSessionFactory(..)
方法設(shè)置你所期望的工廠資源。 以Spring的bean的定義方式,它看上去就像這樣:
<beans> <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="sessionFactory" ref="mySessionFactory"/> </bean> </beans>
這種DAO訪問方式的主要優(yōu)勢在于它僅僅依賴于Hibernate API本身而無需引入任何Spring的類。 從無入侵性的角度來看,這一點非常吸引人。對于Hibernate開發(fā)人員來說也無疑更加自然。
然而,這樣的DAO訪問方式會拋出原生 HibernateException
(這是一個無需聲明或捕獲的unchecked exception),
這意味著,DAO的調(diào)用者只能以致命的錯誤來處理這些異常,除非完全依賴Hibernate自身的異常體系。
因而,除非你將DAO的調(diào)用者綁定到具體的實現(xiàn)策略上去,否則你將無法捕獲特定的異常原因,諸如樂觀鎖異常。
這種折中平衡或許可以被接受,如果你的應(yīng)用完全基于Hibernate或者無需進行特殊的異常處理。
幸運的是,Spring的 LocalSessionFactoryBean
可以在任何Spring的事務(wù)管理策略下,
提供對Hibernate的 SessionFactory.getCurrentSession()
方法的支持,它將返回當(dāng)前受Spring事務(wù)管理(甚至是 HibernateTransactionManager
管理的)的 Session
對象。
當(dāng)然,該函數(shù)的標(biāo)準行為依然有效:返回當(dāng)前與正在進行的JTA事務(wù)(無論是Spring的 JtaTransactionManager
、EJB CMT或者普通的JTA)綁定的 Session
對象。
總體來說,DAO可以基于Hibernate3的原生API實現(xiàn),同時,它依舊需要能夠參與到Spring的事務(wù)管理中。
我們可以在這些低層次的數(shù)據(jù)訪問服務(wù)之上的應(yīng)用程序進行更高層次的事務(wù)劃分,從而讓事務(wù)能夠橫跨多個操作。
這里對于相關(guān)的業(yè)務(wù)邏輯對象同樣沒有實現(xiàn)接口的限制,它只需要一個Spring的 PlatformTransactionManager
。
同SessionFactory一樣,它可以來自任何地方,但是最好是一個經(jīng)由 setTransactionManager(..)
方法注入的bean的引用,正如使用 setProductDao
方法來設(shè)置 productDAO
一樣。
下面演示了在Spring的application context中定義一個事務(wù)管理器和一個業(yè)務(wù)對象,以及具體的業(yè)務(wù)方法實現(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ù)劃分的硬編碼。 這將使你可以從每個業(yè)務(wù)方法中重復(fù)的事務(wù)劃分代碼中解放出來,真正專注于為你的應(yīng)用添加有價值的業(yè)務(wù)邏輯代碼。此外,類似傳播行為和隔離級別等事務(wù)語義能夠在配置文件中改變,而不會影響具體的業(yè)務(wù)對象實現(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)中則嚴格要求被封裝成unchecked exception。
TransactionTemplate
會在一個unchecked exception拋出時,或者當(dāng)事務(wù)被應(yīng)用程序通過 TransactionStatus
標(biāo)記為rollback-only時觸發(fā)一個數(shù)據(jù)庫回滾操作。
TransactionInterceptor
默認進行同樣的操作,但是它允許對每個方法配置自己的rollback策略。
下面列出的高級別的聲明式的事務(wù)管理方案并沒有使用 ProxyFactoryBean
,它將使那些大量的業(yè)務(wù)對象需要被納入到事務(wù)管理中時的配置變得非常簡單。
在你打算繼續(xù)閱讀下一部分之前,我們 強烈 推薦你想去閱讀 第?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ù)處理委托給一個 PlatformTransactionManager
實例來處理。
在Hibernate應(yīng)用中,它可以是一個 HibernateTransactionManager
(對于單獨一個的Hibernate SessionFactory
使用一個 ThreadLocal
的 Session
)或一個 JtaTransactionManager
(代理給容器的JTA子系統(tǒng))。
你甚至可以使用自定義的 PlatformTransactionManager
的實現(xiàn)。因而,在你的應(yīng)用碰到了特定的分布式事務(wù)的部署需求時(類似將原來的Hibernate事務(wù)管理轉(zhuǎn)變?yōu)镴TA),僅僅需要改變配置而已:簡單將Hibernate的事務(wù)管理器替換成JTA事務(wù)實現(xiàn)。
任何的事務(wù)劃分和數(shù)據(jù)訪問的代碼都無需因此而改變,因為他們僅僅使用了通用的事務(wù)管理API。
對于橫跨多個Hibernate SessionFacotry的分布式事務(wù),只需簡單地將 JtaTransactionManager
同多個 LocalSessionFactoryBean
的定義結(jié)合起來作為事務(wù)策略。
你的每一個DAO通過bean屬性得到各自的 SessionFactory
引用。如果所有的底層JDBC數(shù)據(jù)源都是支持事務(wù)的容器,那么只要業(yè)務(wù)對象使用了 JtaTransactionManager
作為事務(wù)策略,它就可以橫跨多個DAO和多個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級別的Hibernate緩存處理,而無需使用與容器相關(guān)的事務(wù)管理器或JCA連接器。(只要不是由EJB發(fā)起的事務(wù))
對于特定的 DataSource
,HibernateTransactionManager
能夠為普通的JDBC訪問代碼提供Hibernate所使用的 Connection
。
這一功能允許你可以完全混和使用Hibernate/JDBC進行數(shù)據(jù)訪問而無需依賴JTA在高層次代碼中進行事務(wù)劃分,只要你依然訪問的是同一個數(shù)據(jù)庫!
HibernateTransactionManager
能夠自動地將Hibernate事務(wù)暴露為JDBC事務(wù),如果你通過設(shè)置 DataSource
來建立SessionFactory
對象(通過設(shè)置 LocalSessionFactoryBean
中的“dataSource”屬性)。
另外一種配置方法是通過設(shè)置 HibernateTransactionManager
的“dataSource”屬性,明確指定事務(wù)需要暴露的 DataSource
。
Spring的資源管理允許你簡單地在一個JNDI的 SessionFactory
和一個本地的 SessionFactory
之間切換而無需更改任何一行應(yīng)用程序代碼。
把資源定義放在容器中還是放在應(yīng)用程序本地中主要是由使用的事務(wù)策略決定的。與Spring定義的本地 SessionFactory
相比,一個手工注冊的JNDI SessionFactory
并沒有體現(xiàn)出多大的優(yōu)勢。
通過Hibernate的JCA連接器來部署一個 SessionFactory
提供了能使之參與到J2EE服務(wù)器管理架構(gòu)的增值服務(wù),不過除此之外也并沒有增加實際的價值。
Spring對事務(wù)管理的支持有一個非常重要的好處:它不依賴于任何容器。使用非JTA的任何其他事務(wù)策略的配置,程序也能在獨立的測試環(huán)境下正常工作。
尤其對于那些典型的單數(shù)據(jù)庫事務(wù)情況下,這將是一個非常輕量級而又強大的JTA替代方案。當(dāng)你使用一個本地的EJB SLSB來驅(qū)動事務(wù)時,即時你可能只訪問一個數(shù)據(jù)庫而且僅僅通過CMT使用SLSB的聲明性事務(wù),你仍然要依賴于EJB容器和JTA。
使用編程式JTA替換方案依然需要J2EE環(huán)境,JTA不僅對于JTA本身,還對于JNDI的 DataSource
實例引入了容器依賴。
對于非Spring環(huán)境的JTA驅(qū)動的Hibernate事務(wù),你必須使用Hibernate JCA連接器或者額外的Hibernate事務(wù)代碼及 TransactionManagerLookup
配置,來恰當(dāng)?shù)靥幚鞪VM級別的緩存。
Spring驅(qū)動的事務(wù)管理與本地定義的 SessionFactory
能夠工作得非常好,就像使用本地的JDBC DataSource
一樣,當(dāng)然前提必須是訪問單個數(shù)據(jù)庫。
因此當(dāng)你真正面對分布式事務(wù)的需求時,可以馬上回到Spring的JTA事務(wù)。必須要注意,一個JCA連接器需要特定容器的部署步驟,而且首先容器要支持JCA。
這要比使用本地資源定義和Spring驅(qū)動事務(wù)來部署一個簡單的Web應(yīng)用麻煩得多。而且你通常需要特定企業(yè)版本的容器,但是像類似WebLogic的Express版本并不提供JCA。
一個僅使用本地資源并且針對一個數(shù)據(jù)庫的事務(wù)操作的Spring應(yīng)用將可以在任何一種J2EE的Web容器中工作(不需要JTA、JCA或者EJB),比如Tomcat、Resin甚至普通的Jetty。
除此之外,這樣的中間層可以在桌面應(yīng)用或測試用例中被簡單地重用。
考慮一下所有的情況:如果你不使用EJB,堅持使用本地 SessionFactory
以及Spring的 HibernateTransactionManager
或者 JtaTransactionManager
,
你將獲得包括適當(dāng)?shù)腏VM級別上的緩存以及分布式事務(wù)在內(nèi)的所有好處,而不會有任何容器部署的麻煩。通過JCA連接器的Hibernate的 SessionFactory
的JNDI注冊僅僅在EJB中使用會帶來好處。
在一些具有非常嚴格的 XADataSource
實現(xiàn)的JTA環(huán)境中(目前來說只是WebLogic和WebSphere的某些版本)當(dāng)你將Hibernate配置成不感知JTA PlatformTransactionManager
對象時,容器可能會在應(yīng)用服務(wù)器日志中報出一些警告和異常。
這些警告和異常通常會說:“正在被訪問的連接不再有效,或者JDBC連接不再有效,可能由于事務(wù)不再處于激活狀態(tài)”。下面是一個從WebLogic上拋出的異常:
java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No further JDBC access is allowed within this transaction.
這樣的錯誤警告很容易解決:只要簡單的把Hibernate配置成感知到JTA PlatformTransactionManager
實例的存在即可,同時將他們進行同步(與Spring同步)??梢杂袃煞N方法達到這種效果:
如果你已經(jīng)在application context中定義并獲取了JTA PlatformTransactionManager
對象(或許來自通過 JndiObjectFactoryBean
得到的JNDI)并已經(jīng)將它注入到類似Spring的 JtaTransactionManager
中,
那么最簡單的方式就是指定這個bean的引用作為 LocalSessionFactoryBean
的 jtaTransactionManager 屬性。Spring將使這個對象被Hibernate所感知。
多數(shù)情況下,你還沒有得到JTA的 PlatformTransactionManager
實例(由于Spring的 JtaTransactionManager
能夠自己找到它),所以你需要自行配置Hibernate并直接尋找資源。
正如Hibernate文檔中提到的,這可以通過在Hibernate配置一個應(yīng)用服務(wù)器特定的 TransactionManagerLookup
類來完成。
對于恰當(dāng)?shù)氖褂梅椒?,你無需了解更多的東西。但是我們在這里將描述一下,對于將Hibernate配置為感知或者不感知JTA的 PlatformTransactionManager
對象這兩種情況下,整個事務(wù)的執(zhí)行順序。
Hibernate被配置成不感知JTA PlatformTransactionManager
的情況下,當(dāng)一個JTA事務(wù)提交時,整個事件的執(zhí)行順序如下:
JTA 事務(wù)提交
Spring的 JtaTransactionManager
同步到JTA事務(wù),它被JTA事務(wù)管理器通過調(diào)用 afterCompletion 執(zhí)行回調(diào)。
在所有其他活動中,這將由Spring向Hibernate觸發(fā)一個回調(diào),通過Hibernate的 afterTransactionCompletion
回調(diào)(用于清除Hibernate緩存),然后緊跟著一個明確的Hibernate Session的 close()
調(diào)用。
這將使Hibernate試圖去關(guān)閉JDBC的Connection。
在某些環(huán)境中,Connection.close()
的調(diào)用將會觸發(fā)一個警告或者錯誤信息。
這是由于特定的應(yīng)用服務(wù)器將不再考慮 Connection
的可用性,因為此時事務(wù)已經(jīng)被提交了。
在Hibernate被配置成感知JTA的 PlatformTransactionManager
的情況下,當(dāng)一個JTA事務(wù)提交時,整個事件的執(zhí)行順序如下:
JTA 事務(wù)準備提交
Spring的 JtaTransactionManager
同步到JTA事務(wù),因而它被JTA事務(wù)管理器通過調(diào)用 beforeCompletion 執(zhí)行回調(diào)。
Spring能感知到Hibernate自身被同步到JTA Transaction,因而表現(xiàn)得與先前那種情況有所不同。
假設(shè)Hibernate的 Session
需要被關(guān)閉,Spring將負責(zé)關(guān)閉它。
JTA 事務(wù)提交
Hibernate將與JTA transaction進行同步,因而它被JTA事務(wù)管理器通過調(diào)用 afterCompletion 執(zhí)行回調(diào),并且適時地清除緩存。