请选择 进入手机版 | 继续访问电脑版
设为首页 收藏本站
开启辅助访问 切换到宽版

新浪微博登陆

只需一步, 快速开始

QQ登录

只需一步,快速开始

切换风格 立即注册 找回密码

Java教程网

新浪微博达人勋

1460

积分

923

帖子

93

贡献

Rank: 6Rank: 6

积分
1460

社区QQ达人最佳新人热心会员推广达人

发表于 2018-7-27 19:55:56 | 显示全部楼层 |阅读模式
Spring提供了一个JmsTransactionManager用于对JMS ConnectionFactory做事务管理。这将允许JMS应用利用Spring的事务管理特性。JmsTransactionManager在执行本地资源事务管理时将从指定的ConnectionFactory绑定一个ConnectionFactory/Session这样的配对到线程中。JmsTemplate会自动检测这样的事务资源,并对它们进行相应操作。
在Java EE环境中,ConnectionFactory会池化Connection和Session,这样这些资源将会在整个事务中被有效地重复利用。在一个独立的环境中,使用Spring的SingleConnectionFactory时所有的事务将公用一个Connection,但是每个事务将保留自己独立的Session。
JmsTemplate可以利用JtaTransactionManager和能够进行分布式的 JMS ConnectionFactory处理分布式事务。
       在Spring整合JMS的应用中,如果我们要进行本地的事务管理的话非常简单,只需要在定义对应的消息监听容器时指定其sessionTransacted属性为true,如:
  1.     <bean id="jmsContainer"
  2.         class="org.springframework.jms.listener.DefaultMessageListenerContainer">
  3.         <property name="connectionFactory" ref="connectionFactory" />
  4.         <property name="destination" ref="queueDestination" />
  5.         <property name="messageListener" ref="consumerMessageListener" />
  6.         <property name="sessionTransacted" value="true"/>
  7.     </bean>
复制代码

该属性值默认为false,这样JMS在进行消息监听的时候就会进行事务控制,当在接收消息时监听器执行失败时JMS就会对接收到的消息进行回滚,对于SessionAwareMessageListener在接收到消息后发送一个返回消息时也处于同一事务下,但是对于其他操作如数据库访问等将不属于该事务控制。
这里我们可以来做一个这样的测试:我们如上配置监听在queueDestination的消息监听容器的sessionTransacted属性为true,然后把我们前面提到的消息监听器ConsumerMessageListener改成这样:
  1. public class ConsumerMessageListener implements MessageListener {
  2.     public void onMessage(Message message) {
  3.             //这里我们知道生产者发送的就是一个纯文本消息,所以这里可以直接进行强制转换,或者直接把onMessage方法的参数改成Message的子类TextMessage
  4.             TextMessage textMsg = (TextMessage) message;
  5.             System.out.println("接收到一个纯文本消息。");
  6.             try {
  7.                 System.out.println("消息内容是:" + textMsg.getText());
  8.                 if (1 == 1) {
  9.                     throw new RuntimeException("Error");
  10.                 }
  11.             } catch (JMSException e) {
  12.                 e.printStackTrace();
  13.             }
  14.     }
  15. }
复制代码
我们可以看到在上述代码中我们的ConsumerMessageListener在进行消息接收的时候抛出了一个RuntimeException,根据我们上面说的,因为我们已经在对应的监听容器上定义了其sessionTransacted属性为true,所以当这里抛出异常的时候JMS将对接收到的消息进行回滚,即下次进行消息接收的时候该消息仍然能够被接收到。为了验证这一点,我们先执行一遍测试代码,往queueDestination发送一个文本消息,这个时候ConsumerMessageListener在进行接收的时候将会抛出一个RuntimeException,已经接收到的纯文本消息将进行回滚;接着我们去掉上面代码中抛出异常的语句,即ConsumerMessageListener能够正常的进行消息接收,这个时候我们再运行一次测试代码,往ConsumerMessageListener监听的queueDestination发送一条消息。如果之前在接手时抛出了异常的那条消息已经回滚了的话,那么这个时候将能够接收到两条消息,控制台将输出接收到的两条消息的内容。具体结果有兴趣的朋友可以自己验证一下。
       如果想接收消息和数据库访问处于同一事务中,那么我们就可以配置一个外部的事务管理同时配置一个支持外部事务管理的消息监听容器(如DefaultMessageListenerContainer)。要配置这样一个参与分布式事务管理的消息监听容器,我们可以配置一个JtaTransactionManager,当然底层的JMS ConnectionFactory需要能够支持分布式事务管理,并正确地注册我们的JtaTransactionManager。这样消息监听器进行消息接收和对应的数据库访问就会处于同一数据库控制下,当消息接收失败或数据库访问失败都会进行事务回滚操作。
  1. <div>
  2. </div><p class="MsoNormal" style="margin: 0cm 0cm 0pt; color: rgb(0, 0, 0); font-family: Helvetica, Tahoma, Arial, sans-serif;"></p>
复制代码

  1.     <bean id="jmsContainer"
  2.         class="org.springframework.jms.listener.DefaultMessageListenerContainer">
  3.         <property name="connectionFactory" ref="connectionFactory" />
  4.         <property name="destination" ref="queueDestination" />
  5.         <property name="messageListener" ref="consumerMessageListener" />
  6.         <property name="transactionManager" ref="jtaTransactionManager"/>
  7.     </bean>
  8.    
  9.     <bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
复制代码
  1.     <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  2.         <property name="dataSource" ref="dataSource"/>
  3.     </bean>

  4.     <jee:jndi-lookup jndi-name="jdbc/mysql" id="dataSource"/>
  5.     <bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

  6.     <tx:annotation-driven transaction-manager="jtaTransactionManager"/>
复制代码
我们可以看到,在这里我们引入了一个jndi数据源,定义了一个JtaTransactionManager,定义了Spring基于注解的声明式事务管理,定义了一个Spring提供的进行Jdbc操作的工具类jdbcTemplate。

       接下来把我们的ConsumerMessageListener改为如下形式:
  1. public class ConsumerMessageListener implements MessageListener {

  2.     @Autowired
  3.     private TestDao testDao;
  4.    
  5.     private int count = 0;
  6.    
  7.     public void onMessage(Message message) {
  8.             //这里我们知道生产者发送的就是一个纯文本消息,所以这里可以直接进行强制转换,或者直接把onMessage方法的参数改成Message的子类TextMessage
  9.             TextMessage textMsg = (TextMessage) message;
  10.             System.out.println(new Date().toLocaleString() + "接收到一个纯文本消息。");
  11.             try {
  12.                 String text = textMsg.getText();
  13.                 System.out.println("消息内容是:" + text);
  14.                 System.out.println("当前count的值是:" + count);
  15.                 testDao.insert(text + count);
  16.                 if (count == 0) {
  17.                     count ++;
  18.                     throw new RuntimeException("Error! 出错啦!");
  19.                 }
  20.             } catch (JMSException e) {
  21.                 e.printStackTrace();
  22.             }
  23.     }

  24. }
复制代码
我们可以看到,在ConsumerMessageListener中我们定义了一个实例变量count,其初始值为0;在onMessage里面,我们可以看到我们把接收到的消息内容作为参数调用了testDao的insert方法;当count值为0,也就是进行第一次消息接收的时候会将count的值加1,同时抛出一个运行时异常。那么我们这里要测试的就是进行第一次接收的时候testDao已经把相关内容插入数据库了,接着在onMessage里面抛出了一个异常同时count加1,我们预期的结果应该是此时数据库进行回滚,同时JMS也回滚,这样JMS将继续尝试接收该消息,此时同样会调用testDao的insert方法将内容插入数据库,再接着count已经不为0了,所以此时将不再抛出异常,JMS成功进行消息的接收,testDao也成功的将消息内容插入到了数据库。要证明这个预期我们除了看数据库中插入的数据外,还可以看控制台的输出,正常情况控制台将输出两次消息接收的内容,且第一次时count为0,第二次count为1。
       TestDao是一个接口,其TestDaoImpl对insert的方法实现如下:
  1.     @Transactional(readOnly=false)
  2.     public void insert(final String name) {
  3.         
  4.         jdbcTemplate.update("insert into test(name) values(?)", name);

  5.     }
复制代码
这里我们使用支持JtaTransactionManager的Weblogic来进行测试,因为是Web容器,所以我们这里定义了一个Controller来进行消息的发送,具体代码如下:
  1. @Controller
  2. @RequestMapping("test")
  3. public class TestController {

  4.     @Autowired
  5.     @Qualifier("queueDestination")
  6.     private Destination destination;
  7.    
  8.     @Autowired
  9.     private ProducerService producerService;
  10.    
  11.     @RequestMapping("first")
  12.     public String first() {
  13.         producerService.sendMessage(destination, "你好,现在是:" + new Date().toLocaleString());
  14.         return "/test/first";
  15.     }
  16. }
复制代码
接下来就是启用Weblogic服务器,进入其控制台,定义一个名叫“jdbc/mysql”的JNDI数据源,然后把该项目部署到Weblogic服务器上并进行启动。接下来我们就可以访问/test/first.do访问到上述first方法。之后控制台会输出如下信息:

655db0fd-c363-3ebb-9165-9e3c59515307.jpg
        我们可以看到当count为0时接收了一次,并随后抛出了异常,之后count为1又接收了一次,这说明在count为0时抛出异常后我们的JMS进行回滚了,那么我们的数据库是否有进行回滚呢?接着我们来看数据库中的内容:


7196d8b4-8947-3ca9-aee2-c0831cc15061.jpg
        我们可以看到数据库表中只有一条记录,而且最后一位表示count的值的为1,这说明在JMS进行消息接收抛出异常时我们的数据库也回滚了。关于使用JtaTransactionManager进行分布式事务管理的问题就说到这里了,有兴趣的朋友可以自己试验一下。








来自群组: java开发组

新浪微博达人勋

2607

积分

651

帖子

652

贡献

Rank: 6Rank: 6

积分
2607

社区QQ达人

发表于 2018-7-27 19:56:20 | 显示全部楼层
专业抢沙发的!哈哈

新浪微博达人勋

2440

积分

613

帖子

609

贡献

Rank: 6Rank: 6

积分
2440
发表于 2018-7-29 09:18:22 | 显示全部楼层
LZ帖子不给力,勉强给回复下吧

新浪微博达人勋

2628

积分

657

帖子

657

贡献

Rank: 6Rank: 6

积分
2628
发表于 2018-7-31 13:41:57 | 显示全部楼层
呵呵。。。

新浪微博达人勋

2678

积分

668

帖子

670

贡献

Rank: 6Rank: 6

积分
2678
发表于 2018-8-1 06:23:22 | 显示全部楼层
为了三千积分!

新浪微博达人勋

2563

积分

640

帖子

641

贡献

Rank: 6Rank: 6

积分
2563
发表于 2018-8-7 01:52:42 | 显示全部楼层
我也来顶一下..

新浪微博达人勋

2484

积分

621

帖子

621

贡献

Rank: 6Rank: 6

积分
2484
发表于 2018-8-7 02:25:54 | 显示全部楼层
顶顶更健康

新浪微博达人勋

5999

积分

1499

帖子

1500

贡献

Rank: 8Rank: 8

积分
5999
发表于 2018-8-9 20:49:36 | 显示全部楼层
求沙发

新浪微博达人勋

6247

积分

1561

帖子

1562

贡献

Rank: 8Rank: 8

积分
6247
发表于 7 天前 | 显示全部楼层
LZ敢整点更有创意的不?兄弟们等着围观捏~

新浪微博达人勋

6035

积分

1508

帖子

1509

贡献

Rank: 8Rank: 8

积分
6035

社区QQ达人

发表于 前天 06:18 | 显示全部楼层
撸过
您需要登录后才可以回帖 登录 | 立即注册 新浪微博登陆

本版积分规则

关闭

站长推荐 上一条 /1 下一条

小黑屋|手机版|Archiver|Java教程网    

GMT+8, 2018-8-17 23:45 , Processed in 0.531250 second(s), 45 queries .

Powered by Discuz X3.2

© 2001-2013 JAVA教程网

快速回复 返回顶部 返回列表