在Spring中使用JTA事务管理

zjuwangleicn 2011-04-02

Spring通过AOP技术可以让我们在脱离EJB的情况下享受声明式事务的丰盛大餐,脱离JavaEE应用服务器使用声明式事务的道路已经畅通无阻。但是很大部分人都还认为脱离JavaEE应用服务器就无法使用JTA事务,这是一个误解。其实,通过配合使用ObjectWeb的JOTM开源项目,不需要JavaEE应用服务器,Spring也可以提供JTA事务。

正因为AOP让Spring拥有了脱离EJB容器的声明式事务能力,而JOTM让我们在脱离JavaEE应用服务器下拥有JTA事务能力。所以,人们将AOP和JOTM称为Java软件开发的两个圣杯。

本文将讲解Spring在不同环境下提供JTA事务的配置过程,这包括:Spring中直接集成JOTM提供JTA事务管理、将JOTM集成到Tomcat中,Spring通过引用TomcatJNDI数据源提供JTA事务管理、引用其他功能完善JavaEE应用服务器所提供的JTA事务管理。

通过集成JOTM,直接在Spring中使用JTA事务

JOTM(JavaOpenTransactionManager)是ObjectWeb的一个开源JTA实现,它本身也是开源应用程序服务器JOnAS(JavaOpenApplicationServer)的一部分,为其提供JTA分布式事务的功能。

Spring2.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

#不使用CAROLJNDI封装器

carol.start.jndi=false

#不启动命名服务器

carol.start.ns=false

3.在MySQL上建立两个数据库

在MySQL数据库中运行SQL脚本,建立topicdb和postdb两个数据库,在topicdb数据库中创建t_topic表,在postdb数据库中创建t_post表。我们希望在这两个数据库上进行JTA事务。SQL脚本如下所示:

/**//*=========创建topicdb数据源及t_topic表==============*/

DROPDATABASEIFEXISTStopicdb;

CREATEDATABASEtopicdbDEFAULTCHARACTERSETutf8;

USEtopicdb;

droptableifexistst_topic;

createtablet_topic

(

topic_idint(11)notnullauto_increment,

forum_idint(11)notnulldefault0,

topic_titlevarchar(100)notnulldefault'',

user_idint(11)notnulldefault0,

topic_timedatetimedefaultNULL,

topic_viewsint(11)default1,

topic_repliesint(11)default0,

primarykey(topic_id)

);

createindexIDX_TOPIC_USER_IDont_topic

(

user_id

);

/*=========创建postdb数据源及t_postdb表==============*/

DROPDATABASEIFEXISTSpostdb;

CREATEDATABASEpostdbDEFAULTCHARACTERSETutf8;

USEpostdb;

createtablet_post

(

post_idint(11)notnullauto_increment,

topic_idint(11)notnulldefault0,

forum_idint(11)notnulldefault0,

user_idint(11)notnulldefault0,

post_texttext,

post_attachblob,

post_timedatetimedefaultNULL,

primarykey(post_id)

);

createindexIDX_POST_TOPIC_IDont_post

(

topic_id

);

4.在Spring配置文件中配置JOTM

代码清单1applicationContext-jta.xml

<beanid="jotm"class="org.springframework.transaction.jta.JotmFactoryBean"/>①JOTM本地实例

②JTA事务管理器

<beanid="txManager"class="org.springframework.transaction.jta.JtaTransactionManager">

<propertyname="userTransaction"ref="jotm"/>②-1:指定userTransaction属性

</bean>

③XAPool配置,内部包含了一个XA数据源,对应topicdb数据库

<beanid="topicDS"class="org.enhydra.jdbc.pool.StandardXAPoolDataSource"

destroy-method="shutdown">

<propertyname="dataSource">

③-1:内部XA数据源

<beanclass="org.enhydra.jdbc.standard.StandardXADataSource"

destroy-method="shutdown">

<propertyname="transactionManager"ref="jotm"/>

<propertyname="driverName"value="com.MySQL.jdbc.Driver"/>

<propertyname="url"value="jdbc:MySQL://localhost:3309/topicdb"/>

</bean>

</property>

<propertyname="user"value="root"/>

<propertyname="password"value="1234"/>

</bean>

④按照③相似的方式配置另一个XAPool,对应postdb数据库,

<beanid="postDS"class="org.enhydra.jdbc.pool.StandardXAPoolDataSource"

destroy-method="shutdown">

<propertyname="dataSource">

<beanclass="org.enhydra.jdbc.standard.StandardXADataSource"

destroy-method="shutdown">

<propertyname="transactionManager"ref="jotm"/>

<propertyname="driverName"value="com.mysql.jdbc.Driver"/>

<propertyname="url"value="jdbc:mysql://localhost:3309/postdb"/>

</bean>

</property>

<propertyname="user"value="root"/>

<propertyname="password"value="1234"/>

</bean>

⑤配置访问topicDB数据源的SpringJDBC模板

<beanid="topicTemplate"

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

<propertyname="dataSource"ref="topicDS"/>

</bean>

⑥配置访问postDB数据源的SpringJDBC模板

<beanid="postTemplate"

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

<propertyname="dataSource"ref="postDS"/>

</bean>

⑦基于topicTemplate数据源的topicDao

<beanid="topicDao"class="com.baobaotao.dao.jdbc.TopicJdbcDao">

<propertyname="jdbcTemplate"ref="topicTemplate"/>

</bean>

⑧基于postTemplate数据源的postDao

<beanid="postDao"class="com.baobaotao.dao.jdbc.PostJdbcDao">

<propertyname="jdbcTemplate"ref="postTemplate"/>

</bean>

⑨进行跨数据库JTA事务的业务类

<beanid="bbtForum"class="com.baobaotao.service.impl.BbtForumImpl">

<propertyname="topicDao"ref="topicDao"/>

<propertyname="postDao"ref="postDao"/>

</bean>

⑩对BbtForumImpl业务类中的@Transaction注解进行驱动

<tx:annotation-driventransaction-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,如④所示。

接下来的配置就顺理成章了,分别使用SpringJDBC的模板类配置DAO类,然后再配置引用DAO类的业务类。关于SpringJDBC的详细内容,参见第10章的内容。

这里,我们使用@Transaction注解对业务类BbtForumImpl进行事务声明,所以通过<tx:annotation-driven/>对此进行驱动,BbtForumImpl的代码如下所示:

代码清单2BbtForumImpl

packagecom.baobaotao.service.impl;

importorg.springframework.transaction.annotation.Transactional;

importcom.baobaotao.dao.PostDao;

importcom.baobaotao.dao.TopicDao;

importcom.baobaotao.domain.Forum;

importcom.baobaotao.domain.Topic;

importcom.baobaotao.service.BbtForum;

@Transactional①事务注解,以便Spring动态织入事务管理功能

publicclassBbtForumImplimplementsBbtForum...{

privateTopicDaotopicDao;

privatePostDaopostDao;

publicvoidaddTopic(Topictopic)throwsException...{②将方法将被施加JTA事务的增强

topicDao.addTopic(topic);

postDao.addPost(topic.getPost());

}

}

BbtForumImpl将Dao类组织起来,PostDao和TopicDao分别访问不同数据库中表,通过Spring注解驱动事务切面的增强后,它们将工作于同一个JTA事务中。

5.在Spring中运行测试

代码清单3TestBbtForumJta

packagecom.baobaotao.service;

importorg.springframework.test.AbstractDependencyInjectionSpringContextTests;

publicclassTestBbtForumJtaextendsAbstractDependencyInjectionSpringContextTests...{

privateBbtForumbbtForum;

privatefinalLoggerlogger=Logger.getLogger(getClass());

publicvoidsetBbtForum(BbtForumbbtForum)...{

this.bbtForum=bbtForum;

}

protectedString[]getConfigLocations()...{

returnnewString[]...{"classpath:applicationContext-jta.xml"};

}

publicvoidtestAddPost()throwsException...{

logger.info("begin........");

Topictopic=newTopic();

topic.setTopicTitle("Title-pfb");

Postpost=newPost();

post.setPostText("postcontent-pfb");

topic.setPost(post);

bbtForum.addTopic(topic);①使用了JTA事务的业务方法

logger.info("end........");

}

}

通过Spring测试类AbstractDependencyInjectionSpringContextTests的支持,很容易编写一个测试类,对启用了JTA事务的BbtForum#addTopic()方法进行测试。建议你将Log4J设置为DEBUG,这样就可以通过丰富的输出日志观测到JTA事务的执行情况。运行这个测试类后,你将可以看到JTA事务被正确实施。

Spring引用Tomcat的JTA事务

Tomcat是Servlet容器,但它提供了JNDI的实现,因此用户可以象在JavaEE应用程序服务器中一样,在Tomcat中使用JNDI查找JDBC数据源。在事务处理方面,Tomcat本身并不支持JTA,但是可以通过集成JOTM达到目的。

如果你的应用最终部署到一个功能齐备的JavaEE应用服务器上,也许你更希望使用JavaEE应用服务器的JTA功能,这样可以利用应用服务器本身许多优化措施。下面,我们让Tomcat通过JNDI开放JOTM的JTA的数据源,进而在Spring容器引用这个JNDI数据源,并在此基础上提供JTA事务。我们所使用的环境是:Tomcat5.5+JOTM2.3。

1.添加所需的JAR文件

将JOTM以下类包添加到<Tomcat安装目录>/common/lib目录中:

jotm.jar

jotm_jrmp_stubs.jar

jotm_iiop_stubs.jar

ow_carol.jar

jta-spec1_0_1.jar

jts1_0.jar

objectweb-datasource.jar

xapool.jar

howl.jar

connector-1_5.jar

同时,还需要添加相应数据库的JDBC驱动类包,例如MySQL的mysql.jar。

2.配置JOTM

新建一个carol.properties配置文件,放置到<Tomcat安装目录>/common/classes目录下,配置文件内容如下:

#JNDI调用协议

carol.protocols=jrmp

#本地RMI调用

carol.jvm.rmi.local.call=true

 #不使用CAROL的JNDI封装器

carol.start.jndi=false

#不启用命名服务器

carol.start.ns=false

#命名工厂类

carol.jndi.java.naming.factory.url.pkgs=org.apache.naming

将carol.start.jndi设置为false,让JOTM不使用CAROLJNDIwrapper,从而可以避免类装载错误的发生。

3.配置Tomcat环境,配置JNDI的数据源

在<Tomcat安装目录>/conf/context.xml文件中添加以下内容:

<Resourcename="jdbc/topicDS"auth="Container"type="javax.sql.DataSource"①-1:JNDI数据源

factory="org.objectweb.jndi.DataSourceFactory"

username="root"password="1234"

driverClassname="com.mysql.jdbc.Driver"

url="jdbc:mysql://localhost:3309/topicdb"

maxActive="30"maxIdle="30"/>

<Resourcename="jdbc/postDS"auth="Container"type="javax.sql.DataSource"①-2:JNDI数据源

factory="org.objectweb.jndi.DataSourceFactory"

username="root"password="1234"

driverClassname="com.mysql.jdbc.Driver"

url="jdbc:mysql://localhost:3309/postdb"

maxActive="30"maxIdle="30"/>

②JOTMJTA事务管理

<Transactionfactory="org.objectweb.jotm.UserTransactionFactory"jotm.timeout="60"/>

在Tomcat中配置两个JNDI数据源,它们分别指向topicdb和postdb数据库,如①处所示。最后配置JOTM的JTA事务管理器,该事务管理器自动对两个JNDI中的数据源应用JTA事务。

4.Spring中相应的配置

让应用服务器提供JNDI数据源管理和JTA事务后,Spring肩上的担子减轻了许多,Spring要做的只是简单地引用JNDI的数据源,并启用JtaTransactionManager就可以了。

代码清单4applicationContext-jta-tomcat.xml:使用应用服务器的JTA支持

<beansxmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:tx="http://www.springframework.org/schema/tx"

xmlns:jee="http://www.springframework.org/schema/jee"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd

http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.0.xsd

http://www.springframework.org/schema/jeehttp://www.springframework.org/schema/jee/spring-jee-2.0.xsd">

 ①使用TomcatJNDI的数据源

<jee:jndi-lookupid="topicDS"jndi-name="java:comp/env/jdbc/topicDS"/>

<jee:jndi-lookupid="postDS"jndi-name="java:comp/env/jdbc/postDS"/>

<beanid="topicTemplate"class="org.springframework.jdbc.core.JdbcTemplate">

<propertyname="dataSource"ref="topicDS"/>

</bean>

<beanid="postTemplate"class="org.springframework.jdbc.core.JdbcTemplate">

<propertyname="dataSource"ref="postDS"/>

</bean>

<beanid="topicDao"class="com.baobaotao.dao.jdbc.TopicJdbcDao">

<propertyname="jdbcTemplate"ref="topicTemplate"/>

</bean>

<beanid="postDao"class="com.baobaotao.dao.jdbc.PostJdbcDao">

<propertyname="jdbcTemplate"ref="postTemplate"/>

</bean>

<beanid="bbtForum"class="com.baobaotao.service.impl.BbtForumImpl">

<propertyname="topicDao"ref="topicDao"/>

<propertyname="postDao"ref="postDao"/>

</bean>

②只需要指定一个JTA事务管理器就可以了,Spring会自动使用Tomcat中的JTA事务功能

<beanid="txManager"class="org.springframework.transaction.jta.JtaTransactionManager"/>

<tx:annotation-driventransaction-manager="txManager"/>

</beans>

在①处,我们通过Springjee命名空间提供的<jee:jndi-lookup>标签获取应用服务器中的JNDI资源,并将它们声明为一个Bean以供持久化模板类引用。

和直接在Spring中使用JOTM不一样,这时,我们仅需要简单地配置一个JtaTransactionManager就可以了,该事务管理器将自动将JTA事务委托给应用程序器。

Spring引用JavaEE应用服务器JTA事务功能和Tomcat+JOTM提供JTA事务功能的配置步骤基本相似,相信大家可以对照这个实例完成相应的配置。另外,这里的实例采用了SpringJDBC作为持久层实现技术,你完全可以通过少量的调整将其应用到JPA、Hibernate、iBatis等持久化实现技术中。

在特定应用服务器使用JTA

一般来说,Spring的事务抽象与应用服务器是无关的。不过,如果你如果希望事务管理器使用特定的UserTransaction和TransactionManager对象(可以被自动探测),以获取更多的增强事务语义。这时,针对不同的JavaEE应用服务器,Spring的事务管理器可以采取不同的配置。

BEAWebLogic

在一个使用WebLogic7.0、8.1或更高版本的环境中,你一般会优先选用特定于WebLogic的WebLogicJtaTransactionManager类来取代基础的JtaTransactionManager类,因为在WebLogic环境中,该类提供了对Spring事务定义的完全支持,超过了标准的JTA语义。你可以使用以下的配置达到目的:

<beanid="txManager"class="org.springframework.transaction.jta.WebLogicJtaTransactionManager"/>

它的特性包括:支持事务名,支持为每个事务定义隔离级别,以及在任何环境下正确地恢复事务的能力。

IBMWebSphere

在WebSphere5.1、5.0和4.x环境下,你可以使用Spring的WebSphereTransactionManagerFactoryBean类。这是一个工厂类,通过WebSphere的静态访问方法(每个版本的WebSphere中都不同)获取到JTATransactionManager实例。一旦通过工厂bean获取到JTATransactionManager实例,就可以使用该实例装配一个Spring的JtaTransactionManagerbean,它封装了JTAUserTransaction,提供增强的事务语义。你可以按以下方式进行配置:

<beanid="wsJtaTm"class="org.springframework.transaction.jta.WebSphereTransactionManagerFactoryBean"/>

<beanid="transactionManager"class="org.springframework.transaction.jta.JtaTransactionManager">

<propertyname="transactionManagerref="wsJtaTm"/>①引用WebSphere的JTA事务管理器

</bean>

小结

你既可以在没有任务应用服务器支持的情况下,直接通过集成JOTM在Spring中使用JTA事务管理,也可以通过引用JavaEE应用服务器的JNDI数据源,利用应用服务器提供的JTA事务功能间接实现Spring中的JTA事务管理。为了利用一些高级JavaEE应用服务器的JTA事务高级功能,你可以通过Spring所提供的特定于应用服务器的JTA事务管理器进行配置

相关推荐