文档章节

Spring整合JMS 之 JmsTransactionManager事务管理

梨加橙
 梨加橙
发布于 2017/06/09 11:27
字数 2064
阅读 56
收藏 0

在Java EE环境中,ConnectionFactory会池化Connection和Session,这样这些资源将会在整个事务中被有效地重复利用。在一个独立的环境中,使用Spring的SingleConnectionFactory时所有的事务将公用一个Connection,但是每个事务将保留自己独立的Session。

JmsTemplate可以利用JtaTransactionManager和能够进行分布式的 JMS ConnectionFactory处理分布式事务。

在Spring整合JMS的应用中,如果我们要进行本地的事务管理的话非常简单,只需要在定义对应的消息监听容器时指定其sessionTransacted属性为true,如:

<bean id="jmsContainer"  
    class="org.springframework.jms.listener.DefaultMessageListenerContainer">  
    <property name="connectionFactory" ref="connectionFactory" />  
    <property name="destination" ref="queueDestination" />  
    <property name="messageListener" ref="consumerMessageListener" />  
    <property name="sessionTransacted" value="true"/>  
</bean>  

该属性值默认为false,这样JMS在进行消息监听的时候就会进行事务控制,当在接收消息时监听器执行失败时JMS就会对接收到的消息进行回滚,对于SessionAwareMessageListener在接收到消息后发送一个返回消息时也处于同一事务下,但是对于其他操作如数据库访问等将不属于该事务控制。

这里我们可以来做一个这样的测试:我们如上配置监听在queueDestination的消息监听容器的sessionTransacted属性为true,然后把我们前面提到的消息监听器ConsumerMessageListener改成这样:

public class ConsumerMessageListener implements MessageListener {  
   
    public void onMessage(Message message) {  
            //这里我们知道生产者发送的就是一个纯文本消息,所以这里可以直接进行强制转换,或者直接把onMessage方法的参数改成Message的子类TextMessage  
            TextMessage textMsg = (TextMessage) message;  
            System.out.println("接收到一个纯文本消息。");  
            try {  
                System.out.println("消息内容是:" + textMsg.getText());  
                if (1 == 1) {  
                    throw new RuntimeException("Error");  
                }  
            } catch (JMSException e) {  
                e.printStackTrace();  
            }  
    }  
   
} 

我们可以看到在上述代码中我们的ConsumerMessageListener在进行消息接收的时候抛出了一个RuntimeException,根据我们上面说的,因为我们已经在对应的监听容器上定义了其sessionTransacted属性为true,所以当这里抛出异常的时候JMS将对接收到的消息进行回滚,即下次进行消息接收的时候该消息仍然能够被接收到。为了验证这一点,我们先执行一遍测试代码,往queueDestination发送一个文本消息,这个时候ConsumerMessageListener在进行接收的时候将会抛出一个RuntimeException,已经接收到的纯文本消息将进行回滚;接着我们去掉上面代码中抛出异常的语句,即ConsumerMessageListener能够正常的进行消息接收,这个时候我们再运行一次测试代码,往ConsumerMessageListener监听的queueDestination发送一条消息。如果之前在接手时抛出了异常的那条消息已经回滚了的话,那么这个时候将能够接收到两条消息,控制台将输出接收到的两条消息的内容。具体结果有兴趣的朋友可以自己验证一下。

如果想接收消息和数据库访问处于同一事务中,那么我们就可以配置一个外部的事务管理同时配置一个支持外部事务管理的消息监听容器(如DefaultMessageListenerContainer)。要配置这样一个参与分布式事务管理的消息监听容器,我们可以配置一个JtaTransactionManager,当然底层的JMS ConnectionFactory需要能够支持分布式事务管理,并正确地注册我们的JtaTransactionManager。这样消息监听器进行消息接收和对应的数据库访问就会处于同一数据库控制下,当消息接收失败或数据库访问失败都会进行事务回滚操作。

<bean id="jmsContainer"  
    class="org.springframework.jms.listener.DefaultMessageListenerContainer">  
    <property name="connectionFactory" ref="connectionFactory" />  
    <property name="destination" ref="queueDestination" />  
    <property name="messageListener" ref="consumerMessageListener" />  
    <property name="transactionManager" ref="jtaTransactionManager"/>  
</bean>  
  
<bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>  

当给消息监听容器指定了transactionManager时,消息监听容器将忽略sessionTransacted的值。 

关于使用JtaTransactionManager来管理上述分布式事务,我们这里也可以来做一个试验。

首先:往Spring配置文件applicationContext.xml中添加如下配置:

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">  
    <property name="dataSource" ref="dataSource"/>  
</bean>  
  
<jee:jndi-lookup jndi-name="jdbc/mysql" id="dataSource"/>  
<bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>  
  
<tx:annotation-driven transaction-manager="jtaTransactionManager"/>  

我们可以看到,在这里我们引入了一个jndi数据源,定义了一个JtaTransactionManager,定义了Spring基于注解的声明式事务管理,定义了一个Spring提供的进行Jdbc操作的工具类jdbcTemplate。

接下来把我们的ConsumerMessageListener改为如下形式:

public class ConsumerMessageListener implements MessageListener {  
   
    @Autowired  
    private TestDao testDao;  
      
    private int count = 0;  
      
    public void onMessage(Message message) {  
            //这里我们知道生产者发送的就是一个纯文本消息,所以这里可以直接进行强制转换,或者直接把onMessage方法的参数改成Message的子类TextMessage  
            TextMessage textMsg = (TextMessage) message;  
            System.out.println(new Date().toLocaleString() + "接收到一个纯文本消息。");  
            try {  
                String text = textMsg.getText();  
                System.out.println("消息内容是:" + text);  
                System.out.println("当前count的值是:" + count);  
                testDao.insert(text + count);  
                if (count == 0) {  
                    count ++;  
                    throw new RuntimeException("Error! 出错啦!");  
                }  
            } catch (JMSException e) {  
                e.printStackTrace();  
            }  
    }  
   
}  

我们可以看到,在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的方法实现如下: 

@Transactional(readOnly=false)  
public void insert(final String name) {  
      
    jdbcTemplate.update("insert into test(name) values(?)", name);  
  
}  

这里我们使用支持JtaTransactionManager的Weblogic来进行测试,因为是Web容器,所以我们这里定义了一个Controller来进行消息的发送,具体代码如下:

@Controller  
@RequestMapping("test")  
public class TestController {  
   
    @Autowired  
    @Qualifier("queueDestination")  
    private Destination destination;  
      
    @Autowired  
    private ProducerService producerService;  
      
    @RequestMapping("first")  
    public String first() {  
        producerService.sendMessage(destination, "你好,现在是:" + new Date().toLocaleString());  
        return "/test/first";  
    }  
      
}  

接下来就是启用Weblogic服务器,进入其控制台,定义一个名叫“jdbc/mysql”的JNDI数据源,然后把该项目部署到Weblogic服务器上并进行启动。接下来我们就可以访问/test/first.do访问到上述first方法。之后控制台会输出如下信息:

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

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

本文转载自:http://elim.iteye.com/blog/1983532

共有 人打赏支持
梨加橙
粉丝 34
博文 56
码字总数 43402
作品 0
南昌
后端工程师
私信 提问
spring mvc 整合 jpa 搭建基于领域驱动模型框架搭建

一、maven pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 h......

漂泊者及其影子
2014/06/16
0
0
Java新手如何学习Spring、Struts、Hibernate三大框架?

ava三大框架的各自作用 一、Spring Spring是一个解决了许多在J2EE开发中常见的问题的强大框架。 Spring提供了管理业务对象的一致方法并且鼓励了注入对接口编程而不是对类编程的良好习惯。Spr...

懿涌
2017/06/02
683
9
一步一步Spring整合JMS

1.1 JMS简介 JMS的全称是Java Message Service,即Java消息服务。它主要用于在生产者和消费者之间进行消息传递,生产者负责产生消息,而消费者负责接收消息。把它应用到实际的业务需求中的话...

摆渡者
2015/08/31
0
0
SpringMVC+Spring4.0+Hibernate 简单的整合

学习的初始 一个 Hello World。 1、搭建好环境 工欲善其事,必先利其 这是需要的jar 简单的说下 : standard.jar 这个jar包是我们在jsp中使用JSTL标签的时候用到的。你也可以使用SpringEL 。 ...

Jeremy_pan
2014/08/30
0
13
spring3 ,spring3 mvc 配置文件整合问题,哪种配置更好?

正常的整合是要在web.xml里面配置spring监听器,spring mvc的dispath servlet,代码如下: Java代码 org.springframework.web.context.ContextLoaderListener contextConfigLocation /WEB-I......

_凤求凰_
2014/01/07
2.4K
3

没有更多内容

加载失败,请刷新页面

加载更多

阿里巴巴的26款超神Java开源项目!

1.分布式应用服务开发的一站式解决方案 Spring Cloud Alibaba Spring Cloud Alibaba 致力于提供分布式应用服务开发的一站式解决方案。此项目包含开发分布式应用服务的必需组件,方便开发者通...

DemonsI
11分钟前
10
0
matlab-线性代数 判断 det 矩阵是否可逆

  matlab : R2018a 64bit     OS : Windows 10 x64 typesetting : Markdown    blog : my.oschina.net/zhichengjiu    gitee : gitee.com/zhichengjiu   code clearclc% 输入需要......

志成就
12分钟前
0
0
mysql 证明为什么用limit时,offset很大会影响性能

本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/117 首先说明一下MySQL的版本: mysql> select version();+-----------+| version() |+-----------+|......

tantexian
20分钟前
0
0
你要的JMeter压力测试教程及结果分析

一、测试工具: JMeter 二、JMeter介绍: Apache JMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试,它最初被设计用于Web应用测试,但后来扩展到其他测试领域。 三、J...

孟飞阳
21分钟前
0
0
css中内容溢出的处理

一.内容溢出时一般显示成省略号的形式。 主要属性设置:1.父级:确定width:number,overflow:hidden ; text-overflow:ellipsise 2.内容:white-wrape:norape; 二:当内容溢出不做特殊处理时,...

hezhongjie
26分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部