JTA事务管理--配置剖析

时间:2022-08-15 06:16:01

概述 
   【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事务被正确实施。