Distributed transactions in Spring, with and without XA
The Spring Framework's support for the Java Transaction API (JTA) enables applications to use distributed transactions and the XA protocol without running in a Java EE container. Even with this support, however, XA is expensive and can be unreliable or cumbersome to administrate. It may come as a welcome surprise, then, that a certain class of applications can avoid the use of XA altogether.
To help you understand the considerations involved in various approaches to distributed transactions, I'll analyze seven transaction-processing patterns, providing code samples to make them concrete. I'll present the patterns in reverse order of safety or reliability, starting with those with the highest guarantee of data integrity and atomicity under the most general circumstances. As you move down the list, more caveats and limitations will apply. The patterns are also roughly in reverse order of runtime cost (starting with the most expensive). The patterns are all architectural, or technical, as opposed to business patterns, so I don't focus on the business use case, only on the minimal amount of code to see each pattern working.
Note that only the first three patterns involve XA, and those might not be available or acceptable on performance grounds. I don't discuss the XA patterns as extensively as the others because they are covered elsewhere, though I do provide a simple demonstration of the first one. By reading this article you'll learn what you can and can't do with distributed transactions and how and when to avoid the use of XA -- and when not to.
Distributed transactions and atomicity
A distributed transaction is one that involves more than one transactional resource. Examples of transactional resources are the connectors for communicating with relational databases and messaging middleware. Often such a resource has an API that looks something like begin()
, rollback()
, commit()
. In the Java world, a transactional resource usually shows up as the product of a factory provided by the underlying platform: for a database, it's a Connection
(produced by DataSource
) or Java Persistence API (JPA) EntityManager
; for Java Message Service (JMS), it's a Session
.
In a typical example, a JMS message triggers a database update. Broken down into a timeline, a successful interaction goes something like this:
- Start messaging transaction
- Receive message
- Start database transaction
- Update database
- Commit database transaction
- Commit messaging transaction
If a database error such as a constraint violation occurred on the update, the desirable sequence would look like this:
[ Learn Java from beginning concepts to advanced design patterns in this comprehensive 12-part course! ]
- Start messaging transaction
- Receive message
- Start database transaction
- Update database, fail!
- Roll back database transaction
- Roll back messaging transaction
In this case, the message goes back to the middleware after the last rollback and returns at some point to be received in another transaction. This is usually a good thing, because otherwise you might have no record that a failure occurred. (Mechanisms to deal with automatic retry and handling exceptions are out of this article's scope.)
The important feature of both timelines is that they are atomic, forming a single logical transaction that either succeeds completely or fails completely.
But what guarantees that the timeline looks like either of these sequences? Some synchronization between the transactional resources must occur, so that if one commits they both do, and vice versa. Otherwise, the whole transaction is not atomic. The transaction is distributed because multiple resources are involved, and without synchronization it will not be atomic. The technical and conceptual difficulties with distributed transactions all relate to the synchronization of the resources (or lack of it).
The first three patterns discussed below are based on the XA protocol. Because these patterns have been widely covered, I won't go into much detail about them here. Those familiar with XA patterns may want to skip ahead to the Shared Transaction Resource pattern.
Full XA with 2PC
If you need close-to-bulletproof guarantees that your application's transactions will recover after an outage, including a server crash, then Full XA is your only choice. The shared resource that is used to synchronize the transaction in this case is a special transaction manager that coordinates information about the process using the XA protocol. In Java, from the developer's point of view, the protocol is exposed through a JTA UserTransaction
.
Being a system interface, XA is an enabling technology that most developers never see. They need to know is that it's there, what it enables, what it costs, and the implications for how they use transactional resources. The cost comes from the two-phase commit (2PC) protocol that the transaction manager uses to ensure that all resources agree on the outcome of a transaction before it ends.
If the application is Spring-enabled, it uses the Spring JtaTransactionManager
and Spring declarative transaction management to hide the details of the underlying synchronization. The difference for the developer between using XA and not using XA is all about configuring the factory resources: the DataSource
instances, and the transaction manager for the application. This article includes a sample application (the atomikos-db
project) that illustrates this configuration. The DataSource
instances and the transaction manager are the only XA- or JTA-specific elements of the application.
To see the sample working, run the unit tests under com.springsource.open.db
. A simple MulipleDataSourceTests
class just inserts data into two data sources and then uses the Spring integration support features to roll back the transaction, as shown in Listing 1:
Listing 1. Transaction rollback
@Transactional
@Test
public void testInsertIntoTwoDataSources() throws Exception { int count = getJdbcTemplate().update(
"INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", 0,
"foo");
assertEquals(1, count); count = getOtherJdbcTemplate()
.update(
"INSERT into T_AUDITS (id,operation,name,audit_date) values (?,?,?,?)",
0, "INSERT", "foo", new Date());
assertEquals(1, count); // Changes will roll back after this method exits }
Then MulipleDataSourceTests
verifies that the two operations were both rolled back, as shown in Listing 2:
Listing 2. Verifying rollback
@AfterTransaction
public void checkPostConditions() { int count = getJdbcTemplate().queryForInt("select count(*) from T_FOOS");
// This change was rolled back by the test framework
assertEquals(0, count); count = getOtherJdbcTemplate().queryForInt("select count(*) from T_AUDITS");
// This rolled back as well because of the XA
assertEquals(0, count); }
For a better understanding of how Spring transaction management works and how to configure it generally, see the Spring Reference Guide.
XA with 1PC Optimization
This pattern is an optimization that many transaction managers use to avoid the overhead of 2PC if the transaction includes a single resource. You would expect your application server to be able to figure this out.
XA and the Last Resource Gambit
Another feature of many XA transaction managers is that they can still provide the same recovery guarantees when all but one resource is XA-capable as they can when they all are. They do this by ordering the resources and using the non-XA resource as a casting vote. If it fails to commit, then all the other resources can be rolled back. It is close to 100 percent bulletproof -- but is not quite that. And when it fails, it fails without leaving much of a trace unless extra steps are taken (as is done in some of the top-end implementations).
Shared Transaction Resource pattern
A great pattern for decreasing complexity and increasing throughput in some systems is to remove the need for XA altogether by ensuring that all the transactional resources in the system are actually backed by the same resource. This is clearly not possible in all processing use cases, but it is just as solid as XA and usually much faster. The Shared Transaction Resource pattern is bulletproof but specific to certain platforms and processing scenarios.
A simple and familiar (to many) example of this pattern is the sharing of a database Connection
between a component that uses object-relational mapping (ORM) with a component that uses JDBC. This is what happens you use the Spring transaction managers that support the ORM tools such as Hibernate, EclipseLink, and the Java Persistence API (JPA). The same transaction can safely be used across ORM and JDBC components, usually driven from above by a service-level method execution where the transaction is controlled.
Another effective use of this pattern is the case of message-driven update of a single database (as in the simple example in this article's introduction). Messaging-middleware systems need to store their data somewhere, often in a relational database. To implement this pattern, all that's needed is to point the messaging system at the same database the business data is going into. This pattern relies on the messaging-middleware vendor exposing the details of its storage strategy so that it can be configured to point to the same database and hook into the same transaction.
Not all vendors make this easy. An alternative, which works for almost any database, is to use Apache ActiveMQ for messaging and plug a storage strategy into the message broker. This is fairly easy to configure once you know the trick. It's demonstrated in this article's shared-jms-db
samples project. The application code (unit tests in this case) does not need to be aware that this pattern is in use, because it is all enabled declaratively in Spring configuration.
A unit test in the sample called SynchronousMessageTriggerAndRollbackTests
verifies that everything is working with synchronous message reception. The testReceiveMessageUpdateDatabase
method receives two messages and uses them to insert two records in the database. When this method exits, the test framework rolls back the transaction, so you can verify that the messages and the database updates are both rolled back, as shown in Listing 3:
Listing 3. Verifying rollback of messages and database updates
@AfterTransaction
public void checkPostConditions() { assertEquals(0, SimpleJdbcTestUtils.countRowsInTable(jdbcTemplate, "T_FOOS"));
List<String> list = getMessages();
assertEquals(2, list.size()); }
The most important features of the configuration are the ActiveMQ persistence strategy, linking the messaging system to the same DataSource
as the business data, and the flag on the Spring JmsTemplate
used to receive the messages. Listing 4 shows how to configure the ActiveMQ persistence strategy:
Listing 4. Configuring ActiveMQ persistence
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"
depends-on="brokerService">
<property name="brokerURL" value="vm://localhost?async=false" />
</bean>
<bean id="brokerService" class="org.apache.activemq.broker.BrokerService" init-method="start"
destroy-method="stop">
... <property name="persistenceAdapter">
<bean class="org.apache.activemq.store.jdbc.JDBCPersistenceAdapter">
<property name="dataSource">
<bean class="com.springsource.open.jms.JmsTransactionAwareDataSourceProxy">
<property name="targetDataSource" ref="dataSource"/>
<property name="jmsTemplate" ref="jmsTemplate"/>
</bean>
</property>
<property name="createTablesOnStartup" value="true" />
</bean>
</property>
</bean>
Listing 5 shows the flag on the Spring JmsTemplate
that is used to receive the messages:
Listing 5. Setting up the JmsTemplate
for transactional use
<beanid="jmsTemplate"class="org.springframework.jms.core.JmsTemplate">
... <!-- This is important... -->
<propertyname="sessionTransacted"value="true"/>
</bean>
Without sessionTransacted=true
, the JMS session transaction API calls will never be made and the message reception cannot be rolled back. The important ingredients here are the embedded broker with a special async=false
parameter and a wrapper for the DataSource
that together ensure that ActiveMQ uses the same transactional JDBC Connection
as Spring.
div {
width: 300px !important;
height: 169px !important;
transition: all 0.5s ease;
}
figure#page-lede.thm-gallery #bcplayer-gallery #bcplayer-gallery_ad > div {
width: 100%;
height: 100%;
transition: all 0.5s ease;
}
-->
Distributed transactions in Spring, with and without XA的更多相关文章
- [官网]How to use distributed transactions with SQL Server on Docker
How to use distributed transactions with SQL Server on Docker https://docs.microsoft.com/en-us/sql/l ...
- Large-scale Incremental Processing Using Distributed Transactions and Notifications
Large-scale Incremental Processing Using Distributed Transactions and Notifications
- Distributed traceability with Spring Cloud: Sleuth and Zipkin
I. Sleuth 0. Concept Trace A set of spans that form a call tree structure, forms the trace of the re ...
- spring Transaction Management --官方
原文链接:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html 12. ...
- Spring系列.事务管理
Spring提供了一致的事务管理抽象.这个抽象是Spring最重要的抽象之一, 它有如下的优点: 为不同的事务API提供一致的编程模型,如JTA.JDBC.Hibernate和MyBatis数据库层 ...
- 数据库分库分表(sharding)系列【转】
原文地址:http://www.uml.org.cn/sjjm/201211212.asp数据库分库分表(sharding)系列 目录; (一) 拆分实施策略和示例演示 (二) 全局主键生成策略 (三 ...
- 数据库分库分表(sharding)系列(四) 多数据源的事务处理
系统经sharding改造之后,原来单一的数据库会演变成多个数据库,如何确保多数据源同时操作的原子性和一致性是不得不考虑的一个问题.总体上看,目前对于一个分布式系统的事务处理有三种方式:分布式事务.基 ...
- 数据库分库分表(sharding)系列
数据库分库分表(sharding)系列 目录; (一) 拆分实施策略和示例演示 (二) 全局主键生成策略 (三) 关于使用框架还是自主开发以及sharding实现层面的考量 (四) 多数据源的 ...
- 基于两阶段提交的分布式事务实现(UP-2PC)
引言:分布式事务是分布式数据库的基础性功能,在2017年上海MySQL嘉年华(IMG)和中国数据库大会(DTCC2018)中作者都对银联UPSQL Proxy的分布式事务做了简要介绍,受限于交流形式难 ...
随机推荐
- 20155305 2016-2017-2 《Java程序设计》实验三 敏捷开发与XP实践
20155305 2016-2017-2 <Java程序设计>实验三 敏捷开发与XP实践 实验内容 XP基础 XP核心实践 相关工具 实验步骤 (一)敏捷开发与XP 1.敏捷开发 敏捷开发 ...
- 【课堂实验】Arrays和String单元测试
实验内容 在IDEA中以TDD的方式对String类和Arrays类进行学习 测试相关方法的正常,错误和边界情况 String类 charAt split Arrays类 sort binarySea ...
- # 20155337 2016-2017-2 《Java程序设计》第九周学习总结
20155337 2016-2017-2 <Java程序设计>第九周学习总结 教材学习内容总结 第16章 JDBC(Java DataBase Connectivity)即java数据库连 ...
- oracle基础命令
oracle使用步骤: 一.oracle安装 两个文件解压到同一文件夹,doc为说明/使用文档 二.oracle启动: 1.启动oracle:启动监听和自定义库 2.启动cmd->sqlplus ...
- TMDXEVM6678L EVM开发板初使用(1)
1. 板子上电风扇转个不停,震动很大. 2. 有点懵逼,第一步干啥,首先安装板子的软件开发包,资料下载地址http://www2.advantech.com/Support/TI-EVM/6678le ...
- 简单读取 properties文件
看了网上很多读取的方法,都太过复杂,直接使用下面的方法就可以简单读取 properties文件了 ide使用idea 测试读取成功 import java.util.ResourceBundle; p ...
- 获取安卓app的appPackage和appActivity
1.需要配置好android的开发环境后,打开cmd命令窗口 2.在命令窗口中输入,adb logcat>D:/log.log,抓取日志 3.运行启动app 4.查看日志log 5.搜索日志的关 ...
- HashMap 和 HashTable 到底哪不同 ?
HashMap 和 HashTable 到底哪不同 ? 2017/05/29 | 分类: 基础技术 | 1 条评论 | 标签: HASHMAP, HASHTABLE 分享到: 原文出处: 程序员赵鑫 ...
- 启动sshd时,报“Could not load host key”错
原文发表于cu:2016-05-24 现象:启动sshd服务时,虽看似服务启动成功,但客户端并不能连接上sshd服务器端.如下: [root@aefe8007a17d ~]# /usr/sbin/ss ...
- Redis5.0:现公测全免费,点击就送,注册账号,即开即用
华为云分布式缓存服务Redis,是华为云服务的一款核心产品. 分布式缓存Redis是一款内存数据库服务,基于双机热备的高可用架构,提供单机.主从.集群等丰富类型的缓存类型. 现推出最新版本Redis5 ...