概述
【IT168 專稿】Spring 通過AOP技術可以讓我們在脫離EJB的情況下享受聲明式事務的豐盛大餐,脫離Java EE應用服務器使用聲明式事務的道路已經暢通無阻。但是很大部分人都還認為脫離Java EE應用服務器就無法使用JTA事務,這是一個誤解。其實,通過配合使用ObjectWeb的JOTM開源項目,不需要Java EE應用服務器,Spring也可以提供JTA事務。
正因為AOP讓Spring擁有了脫離EJB容器的聲明式事務能力,而JOTM讓我們在脫離Java EE應用服務器下擁有JTA事務能力。所以,人們將AOP和JOTM稱為Java軟件開發的兩個聖杯。
本文將講解Spring在不同環境下提供JTA事務的配置過程,這包括:Spring中直接集成JOTM提供JTA事務管理、將JOTM集成到Tomcat中,Spring通過引用Tomcat JNDI數據源提供JTA事務管理、引用其它功能完善JavaEE應用服務器所提供的JTA事務管理。
通過集成JOTM,直接在Spring中使用JTA事務
JOTM(Java Open Transaction Manager)是ObjectWeb的一個開源JTA實現,它本身也是開源應用程序服務器JOnAS(Java Open Application Server)的一部分,為其提供JTA分布式事務的功能。
Spring 2.0附帶的依賴類庫中雖然包含jotm類庫,但是並不完整,你可以到http://jotm.objectweb.org下載完全版的JOTM。
Spring為JOTM提供了一個org.springframework.transaction.jta.JotmFactoryBean支持類,通過該支持類可以方便地創建JOTM本地實例。
下面,我們通過配置,使上節中BbtForumImpl#addTopic()方法工作在JTA事務的環境下。addTopic()內部使用兩個DAO類(TopicDao和PostDao)分別訪問不同數據庫中的表。通過下面的步驟說明了使addTopic()方法擁有JTA事務的整個過程:
1. 將JOTM以下類庫添加到類路徑中:
jotm.jar
xapool.jar
jotm_jrmp_stubs.jar
jta-spec1_0_1.jar
connector-1_5.jar
2. 編寫JOTM配置文件,放到類路徑下
carol.properties
#JNDI調用協議
carol.protocols=jrmp
#不使用CAROL JNDI封裝器
carol.start.jndi=false
#不啟動命名服務器
carol.start.ns=false
3. 在MySQL上建立兩個數據庫
在MySQL數據庫中運行SQL腳本,建立topicdb和postdb兩個數據庫,在topicdb數據庫中創建t_topic表,在postdb數據庫中創建t_post表。我們希望在這兩個數據庫上進行JTA事務。
4. 在Spring配置文件中配置JOTM
代碼清單 1 applicationContext-jta.xml
…
<!--①JOTM本地實例 --> <bean id="jotm" class="org.springframework.transaction.jta.JotmFactoryBean" /> <!--②JTA事務管理器 --> <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="userTransaction" ref="jotm" /> <!--②-1:指定userTransaction屬性--> </bean> <!--③XAPool配置,內部包含了一個XA數據源,對應topicdb數據庫--> <bean id="topicDS" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource" destroy-method="shutdown"> <property name="dataSource"> <!--③-1:內部XA數據源 --> <bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown"> <property name="transactionManager" ref="jotm" /> <property name="driverName" value="com.MySQL.jdbc.Driver" /> <property name="url" value="jdbc:MySQL://localhost:3309/topicdb" /> </bean> </property> <property name="user" value="root" /> <property name="password" value="1234" /> </bean> <!--④按照③相似的方式配置另一個XAPool,對應postdb數據庫, --> <bean id="postDS" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource" destroy-method="shutdown"> <property name="dataSource"> <bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown"> <property name="transactionManager" ref="jotm" /> <property name="driverName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3309/postdb" /> </bean> </property> <property name="user" value="root" /> <property name="password" value="1234" /> </bean> <!--⑤配置訪問topicDB數據源的Spring JDBC模板 --> <bean id="topicTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="topicDS" /> </bean> <!--⑥配置訪問postDB數據源的Spring JDBC模板 --> <bean id="postTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="postDS" /> </bean> <!--⑦基於topicTemplate數據源的topicDao --> <bean id="topicDao" class="com.baobaotao.dao.jdbc.TopicJdbcDao"> <property name="jdbcTemplate" ref="topicTemplate" /> </bean> <!--⑧基於postTemplate數據源的postDao --> <bean id="postDao" class="com.baobaotao.dao.jdbc.PostJdbcDao"> <property name="jdbcTemplate" ref="postTemplate" /> </bean> <!--⑨進行跨數據庫JTA事務的業務類--> <bean id="bbtForum" class="com.baobaotao.service.impl.BbtForumImpl"> <property name="topicDao" ref="topicDao" /> <property name="postDao" ref="postDao" /> </bean> <!--⑩對BbtForumImpl業務類中的@Transaction注解進行驅動,以織入事務管理切面 --> <tx:annotation-driven transaction-manager="txManager" />
首先,我們在①處通過Spring所提供的JotmFactoryBean創建一個本地JOTM實例,該實例同時實現了 javax.transaction.UserTransaction和javax.transaction.TransactionManager接口,它可以和ObjectWeb的XAPool一起工作。
JTA事務管理器通過userTransaction屬性引用本地JOTM實例,Spring的JtaTransactionManager會自動探測到傳入的javax.transaction.UserTransaction引用也實現了javax.transaction.TransactionManager,所以我們無需再配置JtaTransactionManager的transactionManager屬性,如②所示。
在Spring中配置JOTM的另一個關鍵問題是配置XAPool,支持JTA事務的數據源必須封裝成XAPool。首先,我們通過org.enhydra.jdbc.standard.StandardXADataSource 配置一個XA數據源,它指向topicdb數據庫,如③-1所示。而后,通過org.enhydra.jdbc.pool.StandardXAPoolDataSource將其封裝成一個XAPool,如③所示。按照相同的方式,配置指向postdb數據庫的XAPool,如④所示。
接下來的配置就順理成章了,分別使用Spring JDBC的模板類配置DAO類,然后再配置引用DAO類的業務類。關於Spring JDBC的詳細內容,參見第10章的內容。
這里,我們使用@Transaction注解對業務類BbtForumImpl進行事務聲明,所以通過<tx:annotation-driven/>對此進行驅動,BbtForumImpl的代碼如下所示:
代碼清單 2 BbtForumImpl
package com.baobaotao.service.impl; import org.springframework.transaction.annotation.Transactional; import com.baobaotao.dao.PostDao; import com.baobaotao.dao.TopicDao; import com.baobaotao.domain.Forum; import com.baobaotao.domain.Topic; import com.baobaotao.service.BbtForum; @Transactional// ①事務注解,以便Spring動態織入事務管理功能 public class BbtForumImpl implements BbtForum { private TopicDao topicDao; private PostDao postDao; public void addTopic(Topic topic) throws Exception { //②將方法將被施加JTA事務的增強 topicDao.addTopic(topic); postDao.addPost(topic.getPost()); } }
BbtForumImpl將Dao類組織起來,PostDao和TopicDao分別訪問不同數據庫中表,通過Spring注解驅動事務切面的增強后,它們將工作於同一個JTA事務中。
5. 在Spring中運行測試
代碼清單 3 TestBbtForumJta
package com.baobaotao.service; import org.springframework.test.AbstractDependencyInjectionSpringContextTests; public class TestBbtForumJta extends AbstractDependencyInjectionSpringContextTests{ private BbtForum bbtForum; private final Logger logger = Logger.getLogger(getClass()); public void setBbtForum(BbtForum bbtForum) { this.bbtForum = bbtForum; } protected String[] getConfigLocations() { return new String[]{"classpath:applicationContext-jta.xml"}; } public void testAddPost() throws Exception{ logger.info("begin........"); Topic topic = new Topic(); topic.setTopicTitle("Title -pfb"); Post post = new Post(); post.setPostText("post content -pfb"); topic.setPost(post); bbtForum.addTopic(topic); //①使用了JTA事務的業務方法 logger.info("end........"); } }
通過Spring測試類AbstractDependencyInjectionSpringContextTests的支持,很容易編寫一個測試類,對啟用了JTA事務的BbtForum#addTopic()方法進行測試。建議你將Log4J設置為DEBUG,這樣就可以通過豐富的輸出日志觀測到JTA事務的執行情況。運行這個測試類后,你將可以看到JTA事務被正確實施。