原文链接:
http://www.javaworld.com/javaworld/jw-01-2009/jw-01-spring-transactions.html?page=6
Page 6 of 6
这个用于触发回滚的确切机制并不重要;有若干可选方案。重要的是提交或者回滚动作的发生和资源的业务操作顺序相反。在示范应用中,消息事务必须最后提交,因为业务处理的指令包含在那个资源中。之所以这很重要,是由于在这个失败(很少发生)中第一个提交成功第二个失败。由于从设计上而言,所有的业务处理在这个时间点已经结束,出现部分失败的唯一原因只能是消息中间件的基础设施出了问题。
注意如果数据库资源提交失败,那么效应只是一个回滚。所以仅有的非原子性失败模式是第一个事务提交后第二个进行回滚。更一般的,如果事务中有n个资源,那么有n-1个这样的失败模式,导致回滚某数据后部分资源处于一个不一致(已提交)的状态。在上面这个消息-数据库的案例中,
这种故障模式的结果是,该消息被回滚,然后回到另一个事务,即使它已被成功地处理了。因此你可以放心的假设可能发生的更糟糕的事情是,消息被重复传递。更一般的情况,由于事务前期的资源被认为可能潜在包含后续资源的处理信息,最终结果一般被称为消息重复(duplicate message)。
一些人觉得消息重复很少发生所以无需重视。但为了对业务数据的正确性和一致性更为自信,你需要在业务逻辑中意识到这一点。如果业务处理意识到消息会重复发送,要做的不过是(通常会有一些额外的成本,但远少于2PC)是检查数据是否已被处理,如果有则什么也不做。这在专业上有时被称为幂等业务服务(Idempotent Business Service)模式。
示例代码包括利用该模式同步事务性资源的两个例子。我将依次讨论它们,然后看看其他一些可选项。
Spring和消息驱动POJOs(Spring and message-driven POJOs)
在示例代码best-jms-db项目中,参与者都使用主流的配置选项,遵循最大努力单阶段提交(Best Efforts 1PC)模式。想法是发送到一个队列中的消息被异步侦听器所选择,并且用于将数据插入到数据库中的表。
TransactionAwareConnectionFactoryProxy
-- Spring中被用于这个模式的一个部件 -- 在这里是关键因素。这个配置把ConnectionFactory包装在一个装饰器中来处理事务同步,而不是使用原有的ConnectionFactory。具体细节在文件jms-context.xml中,如列表6所示:
列表6. 配置一个TransactionAwareConnectionFactoryProxy
来包装ActiveMQ提供的JMS ConnectionFactory
<bean id="connectionFactory"
class="org.springframework.jms.connection.TransactionAwareConnectionFactoryProxy">
<property name="targetConnectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory" depends-on="brokerService">
<property name="brokerURL" value="vm://localhost"/>
</bean>
</property>
<property name="synchedLocalTransactionAllowed" value="true" />
</bean>
ConnectionFactory不需要 知道和哪个 事务 管理器同步 , 因为当需要时只有一个 事务 处于激活状态 , 这是在Spring 内部处理的 。 驱动 (driving)事务 是由一个 通用 的DataSourceTransactionManager 配置在 data-source-context.xml 中。 这个组件需要 了解 事务管理器是 JMS 侦听容器, 将 轮询 和接收消息:
<jms:listener-container transaction-manager="transactionManager" >
<jms:listener destination="async" ref="fooHandler" method="handle"/>
</jms:listener-container>
fooHandler
和method告诉侦听容器在一个“异步”消息到来时该调用哪个组件的哪个方法。该处理器程序实现如下,接受一个字符串作为输入信息,并用它来插入一个记录:
public void handle(String msg) {
jdbcTemplate.update(
"INSERT INTO T_FOOS (ID, name, foo_date) values (?, ?,?)", count.getAndIncrement(), msg, new Date());
}
为模拟失败,这个代码使用了一个
FailureSimulator
切面(aspect)。
它检查消息内容是否会失败,以什么样的方式失败。列表7中的maybeFail()方法,在FooHandler处理完消息,但在事务结束前被调用,因此它可以影响事务的结果:
列表7. maybeFail()
方法
@AfterReturning("execution(* *..*Handler+.handle(String)) && args(msg)")
public void maybeFail(String msg) {
if (msg.contains("fail")) {
if (msg.contains("partial")) {
simulateMessageSystemFailure();
} else {
simulateBusinessProcessingFailure();
}
}
}
simulateBusinessProcessingFailure () 方法在数据库访问失败时 抛出一个 DataAccessException 。 当这个方法 被触发 时, 期望 全部回滚 所有 数据库和消息事务 。 这种场景在 示范 项目的
AsynchronousMessageTriggerAndRollbackTests
单元
测试中测试过。
该simulatemessagesystemfailure()法模拟了一个失败的消息传递系统的削弱潜在的JMS会话。预期的结果,这是一部分提交:数据库的工作保持承诺但消息回滚。这是在asynchronousmessagetriggerandpartialrollbacktests单元测试。
示例包还包括一个完全成功提交的事务的单元测试,在AsynchronousMessageTriggerSunnyDayTests
类中。
同一个JMS配置和相同的业务逻辑,也可用于同步设置,这里消息是在一个锁住的业务逻辑内部调用中被接收,而不是代理给一个侦听容器。这种方法在best-jms-db
示范项目中演示过。顺利提交和完整回滚的案例分别在SynchronousMessageTriggerSunnyDayTests和SynchronousMessageTriggerAndRollbackTests
中被测试。
by iefreer