Spring提供了一个JmsTransactionManager用于对JMS ConnectionFactory做事务管理。这将允许JMS应用利用Spring的事务管理特性。JmsTransactionManager在执行本地资源事务管理时将从指定的ConnectionFactory绑定一个ConnectionFactory/Session这样的配对到线程中。JmsTemplate会自动检测这样的事务资源,并对它们进行相应操作。
在Spring整合JMS的应用中,如果我们要进行本地的事务管理的话非常简单,只需要在定义对应的消息监听容器时指定其sessionTransacted属性为true。
xml: 增加<property name="sessionTransacted" value="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"/>
<!--应答模式是 INDIVIDUAL_ACKNOWLEDGE-->
<!--AUTO_ACKNOWLEDGE = 1 自动确认
CLIENT_ACKNOWLEDGE = 2 客户端手动确认
DUPS_OK_ACKNOWLEDGE = 3 自动批量确认
SESSION_TRANSACTED = 0 事务提交并确认
INDIVIDUAL_ACKNOWLEDGE = 4 单条消息确认-->
<property name="sessionAcknowledgeMode" value="4"/>
<!--对消息开启事务模式-->
<property name="sessionTransacted" value="true"/>
</bean>
监听器加入session.rollback();消息进行回滚重传(上一篇中因为还没有学习回滚,所以用的session.recover()实现的重发,现在就可以使用事务rollback()方法实现了)
回滚的过程是消息先出列,然后重发,默认6次,超过次数后进入到死亡队列,(配置持久化数据库的时候,并持久化到数据库一条数据);
回滚肯定是开启了事务的情况下,那么没有开启事务的情况呢?消息没有确认的情况,消息会停在消息队列中,等待着再次被监听,除非调用session.recover()方法,效果和开启事务并回滚一样会进入死亡队列。
session.rollback();//手动的调用此方法进行回滚,抛出异常时实际上事务开启后会自动进行回滚的。
调用commit()方法进行提交:值得说的是,在事务模式下,在接收消息没有确认的情况也会出列。完成消息。
session.commit();
也就是说,开启事务后消息永远不会出现停留在队列的情况,消息会回滚重发,最后到死亡队列中,而不开启事务的情况,只要不使用session.recover();消息会停留在队列中,不会重发,直至被确认出列。如果调用了recover就和回滚重发一样了。
我们在实际业务中,接收消息操作和数据库操作要在一起进行那么该怎么控制事务呢?
Spring里,如果同时存在JMS操作和DB操作,大概也就三种方式:
1.没有使用JTA。JMS不在事务中,DB操作在事务中
a,消息处理
b,开始数据库事务
c,数据库操作
d,数据库提交
成功:结束
失败:回到b重试
这种方式事务没有集成,靠的纯粹是我们程序的控制,如果最终数据库提交都没成功的话,可以记下log,再人工去纠正数据。例子里把数据库操作放在了更重要的位置,其实也可以倒过来,让数据库操作先完成,只好在做jms操作,看业务需求了:
a,开始数据库事务
b,数据库操作
c,数据库提交
d,消息处理
成功:结束
失败:回到d重试
我们只要把spring配置改成
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
...
<!-- This is important... -->
<property name="sessionTransacted" value="false" />
</bean>
再加上自己程序控制就好了。
2.如果没有使用JTA,对于上面的配置如果把属性 sessionTransacted 设成true的话,就产生了第二种方式,过程:
a,开始jms事务
b,开始数据库事务
c,数据库和jms操作
d,提交数据库操作
e,提交jms操作
a,b的顺序不肯定,不过对程序没影响,但是d,e的顺序是确定的。在Spring里,数据库会在spring的commit方法里提交,而JMS会在afterCommit方法里提交。如果数据库失败,JMS当然也就回滚了,但是如果数据库成功而JMS失败,就产生了数据不一致,就要加上其它措施。而且这里还有一个致命缺点,就是某些情况下即使JMS失败(比如JMS服务器down了),Spring也不会抛出异常,程序外部以为一切正常,而事实上以产生了不一致问题,而且很难发现。
3.如果使用了JTA那么把属性 sessionTransacted 设成true的话,
JMS和数据库操作就在同一个事务里了,没什么好说,最安全的方式,但是效率很低。