β

Java全局事务处理之一——JBoss+Spring+JMS+MySQL

博客园_无知者云 1172 阅读

通常情况下,我们的业务操作只需要访问一种类型的数据源,比如只访问数据库或者消息机制等。在这些情况处理事务,我们只需要数据源自身提供的事务处理机制就可以了,比如JDBC就自己带有commit和rollback功能。这种事务处理机制也叫做本地事务,然而,如果我们的业务操作需要同时操作多种类型的数据源,比如同时访问JMS和数据库,也或者是访问两种不同类型的数据库时,单单依靠这些数据源本身提供的事务处理功能就不够了,此时我们需要全局事务(也叫分布式事务)。

在Java EE中,全局事务是通过JTA来完成的,有关全局事务的原理请参考笔者的这篇博客。在本文中,我们要达到这么一种业务应用场景:从消息机制中读取一条消息(Message),提取其中的数据,再向数据库中插入所读取的数据。如果整个业务过程执行成功,那么消息机制中的该条消息将不再存在(即不能再被其他的消息消费方所读取),数据库中也应该有相应的数据;如果失败,事务将回滚,此时该条消息将继续存在以供下次消费,数据库中也不应该新添加任何数据。

我们将使用JBoss 5作为应用服务器,并且使用JBoss提供的Queue和全局事务管理,另外使用MySQL数据库,并在JBoss配置MySQL的XA Datasource。我们所演示的例子是一个Spring MVC项目。

请下载本文的Github源代码:https://github.com/davenkin/jms-db-jta.git

(一)准备工作

(1)安装JBoss 5。这个简单,从JBoss官网上下载JBoss 5的安装包,并将其解压到某个目录下,然后设置环境变量JBOSS_HOME为JBoss的安装目录,并将$JBOSS_HOME/bin添加到PATH环境变量中。

(2)在JBoss中配置Queue。修改$JBOSS_HOME/server/default/deploy/messaging/destinations-service.xml文件,向其中添加一个名为MyQueue的Queue:

<mbean code="org.jboss.jms.server.destination.QueueService"

      name="jboss.messaging.destination:service=Queue,name=MyQueue"

      xmbean-dd="xmdesc/Queue-xmbean.xml">

  <depends optional-attribute-name="ServerPeer">jboss.messaging:service=ServerPeer</depends>

  <depends>jboss.messaging:service=PostOffice</depends>

</mbean>

之后,在我们的Spring MVC项目中,我们便可以通过JNDI名/queue/MyQueue对该Queue进行访问。

(3)创建MySQL数据库。在MySQL中创建名为spring(名字可以任取)的数据库,再创建名为student的表:

create table student (

name char(10),

id char(10)

);

(4)在JBoss配置MySQL数据源。创建好MySQL数据库和表之后,我们需要在JBoss中配置MySQL的XA Datasource。创建名为mysql-ds.xml文件并将其放在$JBOSS_HOME/server/default/deploy目录下:

<?xml version="1.0" encoding="UTF-8"?>

<datasources>

    <xa-datasource>

        <jndi-name>MySQLDS</jndi-name>

        <xa-datasource-property name="URL">jdbc:mysql://localhost:3306/spring</xa-datasource-property>

        <xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>

        <user-name>root</user-name>

        <password></password>

        <track-connection-by-tx>true</track-connection-by-tx>

        <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>

        <valid-connection-checker-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLValidConnectionChecker</valid-connection-checker-class-name>

        <min-pool-size>1</min-pool-size>

        <max-pool-size>10</max-pool-size>

        <idle-timeout-minutes>10</idle-timeout-minutes>

        <metadata>

            <type-mapping>mySQL</type-mapping>

        </metadata>

    </xa-datasource>

</datasources>

请注意这里的<user-name>和<password>,读者应该根据自己的数据库填入相应的用户名和密码。

(5)验证以上配置是否成功。运行$JBOSS_HOME/bin/run.sh(Windows用户请运行run.bat),然后访问http://localhost:8080/jmx-console/,在打开的网页中,应该能查找到“MyQueue”和“MySQLDS”等字样,分别表示Queue和MySQL数据源配置成功。

(二)在Spring中使用全局事务

在本文的Spring MVC项目例子中,我们定义了一个StudentService,其中的moveStudentFromQueueToDB()负责从Queue中读取一条消息,并将其中的数据写到数据库中,由于该操作需要同时访问JMS和数据库,因此该方法需要全局事务进行管理。StudentService被StudentController所使用,StudentController即为Spring MVC的Controller类,由此我们可以通过访问网页的方式调用StudentService。

在Spring中使用JTA是非常简单的,我们只需要配置一个 <tx:jta-transaction-manager/>,它的作用是通知Spring使用Spring自身提供的JtaTransactionManager,该JtaTransactionManager进而通过JTA规范所约定好的JNDI名字查找应用服务器所提供的TransactionManager,不同的应用服务器所提供的JNDI名字可能会有稍微的不同,不过Spring已经为我们处理好了这些,因此我们不用担心。

另外,我们还需要在Spring中通过JNDI查找MySQL数据源、JMS的ConnectionFactory和Queue:

   <jee:jndi-lookup id="dataSource" jndi-name="java:MySQLDS"/>

   <jee:jndi-lookup id="connectionFactory" jndi-name="java:/XAConnectionFactory"/>

   <jee:jndi-lookup id="queue" jndi-name="/queue/MyQueue"/>

在StudentService中,我们使用到了Spring的JmsTemplate:

   <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">

       <property name="connectionFactory" ref="connectionFactory"/>

       <property name="defaultDestination" ref="queue"/>

       <property name="receiveTimeout" value="5000"/>

       <property name="sessionTransacted" value="true"/>

   </bean>

需要注意的是,我们需要将JmsTemplate的sessionTransacted设置成true,这样JmsTemplate才能参与全局事务处理。此外,由于我们使用了注解(Annotation)的方式来声明事务处理边界,因此我们需要在Spring配置文件中加入:

   <tx:annotation-driven/>

DefaultStudentService实现了StudentService接口,其moveStudentFromQueueToDB()方法标有@Transactional注解,表示该方法将被Spring的事务机制所管理,在本例中即被Spring的JtaTransactionManager所管理。

(三)运行及测试

运行gradle assemble,将生成的war文件拷贝到$JBOSS_HOME/server/default/deploy文件夹下,如果JBoss正在运行,那么它将自动对该war文件进行热部署。

(1)向Queue中插入消息。你可以通过任一种自己熟悉的方式向MyQueue中插入消息,另外,本文的示例代码为你提供了一种简单的方式:访问http://localhost:8080/jms-db-jta-1.0-SNAPSHOT/insertQ,每次访问该URL,项目代码都会随机地产生一条消息,然后将其插入到MyQueue中。此时你可以在http://localhost:8080/jmx-console/中看到刚才所插入的消息:点击“name=MyQueue,service=Queue”,在新的页面中可以看到MessageCount为1,表示有一条新的Message插入到Queue中。

(2)消费该Message,将数据插入数据库。访问http://localhost:8080/jms-db-jta-1.0-SNAPSHOT/fromQueueToDB,此时,我们可以看到MessageCount已经变成了0,表示刚才那条Message已经被消费,再查询MySQL数据库,我们可以看到数据库中多了一条记录,表示此时的全局事务提交成功。

(3)在程序中制造异常,查看全局事务失败的情况。在DefaultStudentService中人为地抛出异常:

   @Override

   @Transactional

   public void moveStudentFromQueueToDB() throws JMSException {

       String nameAndId = ((TextMessage) jmsTemplate.receive()).getText();

       insertStudent(nameAndId, nameAndId);

       throw new RuntimeException("xxxxx");

   }

再重复以上的(1)和(2),此时我们应该能够看到,MyQueue中的Message依然存在,并且数据库中也没有新增数据记录,表示此时的全局事务回滚成功。

(4)如果将上文中的JmsTemplate的sessionTransacted设置成false,在从MyQueue中读取Message之后,即便全局事务回滚,该Message也不会留在MyQueuezhong中,对此,读者可以自行验证。

发表评论