• Distributed Transactions in Spring, with and Without XA-Part I
  • By David Syer
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: JackEggie
  • Proofreader: FireairForce
  • Spring’s distributed transaction implementation — with and without XA — Part 1
  • Spring’s distributed transaction implementation — with and without XA — Part 2
  • Spring’s distributed transaction implementation — with and without XA — Part 3

Spring’s seven transaction modes

Although distributed transactions are typically implemented in Spring using the Java Transaction API and XA protocol, there are other implementations as well. The best way to do this depends on the type of resources your application uses, and whether you’re willing to make trade-offs between performance, security, reliability, and data integrity. To address this typical problem in Java, Spring developer David Syer will present seven implementations of Spring distributed applications, three using the XA protocol and four using other implementations. (Middle Level knowledge)

The Spring framework’s support for the Java Transaction API (JTA) enables applications to use distributed transactions and the XA protocol without needing to be in a Java EE container. However, even with this support, XA’s performance overhead is still high and can be unreliable and difficult to manage. Surprisingly, however, a certain type of application can completely avoid using XA to implement distributed transactions.

To help you understand and think about the various implementations of distributed transactions, I will examine each of the seven transaction processing patterns in detail, along with code examples to help you understand them more concretely. I’ll introduce these patterns in order of security and reliability, starting with the one that generally has the highest degree of data integrity and atomicity. As you browse through the sequence, you’ll see more and more warnings and restrictions. The performance overhead of these schemas is also roughly reversed (starting with the most expensive schema). Unlike writing business code, these patterns are based on architectural complexity and technical difficulty, so I don’t care about business use cases, just the minimum amount of code needed to make each pattern work.

Note that only the first three patterns involve XA. From a performance perspective, these patterns may be unusable or unacceptably poor. I won’t go into the XA pattern in as much detail as I did with the others, because XA has been covered quite a bit elsewhere, but I’ll provide a simple example of the first (XA-BASED) pattern. By reading this article, you’ll learn what you can and can’t do with distributed transactions, when to use XA, when not to use XA, and how to avoid it.

Distributed transactions and their atomicity

A distributed transaction typically contains multiple transaction resources. A transactional resource is a connection between a relational database and message-oriented middleware. A typical transactional resource has apis like BEGIN (), rollback(), and commit(). In Java, a transactional resource is typically represented as an instance provided by the underlying Connection factory: in the case of a database, a Connection object (provided by a DataSource) or an EntityManager object from the Java Persistence API (JPA); For Java Message Service (JMS), this is the Session object.

In a typical example, a JMS message triggers a database update. In chronological order, a successful interaction is as follows:

  1. Start a message transaction
  2. Receives the message
  3. Start a database transaction
  4. Updating the database
  5. Commit a database transaction
  6. Commit message transaction

If the database encounters an error (such as a constraint conflict) while updating the data, the ideal interaction order is as follows:

  1. Start a message transaction
  2. Receives the message
  3. Start a database transaction
  4. Database update failed!
  5. Roll back the database transaction
  6. Rollback the message transaction

In this example, the message goes back to the middleware after the final rollback is complete, and at some point it will be committed again to another transaction. This is usually a good thing, because any errors that occur while updating the data will be recorded if you do so. (Mechanisms for automatic retry and exception handling are beyond the scope of this article.)

The most important feature in both of these examples is atomicity, which logically means that a transaction either succeeds completely or fails completely.

So what guarantees process consistency between the two examples above? We have to do some synchronization between transaction resources so that after one transaction commits, another transaction can commit. Otherwise, the whole transaction is not atomic. Because multiple resources are involved, transactions are distributed. Without synchronization, transactions would not be atomic. The theoretical and implementation difficulties of distributed transactions are related to the synchronization (or lack thereof) of resources.

The first three patterns discussed below are based on the XA protocol. I won’t go into the details here because these patterns are so ubiquitous. If you’re familiar with the XA pattern, you can jump right into the shared transaction resource pattern.

Complete XA protocol with Two-phase Commit (2PC)

If you need to ensure that your application’s transactions can be restored after a server outage (a server crash or power outage), the full XA protocol is your only option. In the following example, the shared resource used to synchronize the transaction is a special transaction manager that coordinates the process information using the XA protocol. In Java, from a developer’s point of view, the protocol is exposed through JTA’s UserTransaction object.

As a system interface, XA is an underlying technology that most developers have never seen before. Developers need to know the XA protocol exists, what it can do, what the performance costs are, and how it manipulates transactional resources. The performance 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 the transaction ends.

If the application is built on Spring, it uses JtaTransactionManager and Spring declarative transaction Management in Spring to hide the details of the underlying synchronization. For developers, the use of XA depends on how the factory resources are configured: how the DataSource instance and transaction manager are configured in the application. This article includes a sample application (the Atomikos-DB project) that demonstrates this configuration approach. Only the DataSource instance and transaction manager in the application are XA or JTA based.

To see how the example works, run the unit tests under com.springsource.open.db. The MulipleDataSourceTests class inserts data into the two data sources and then rolls back the transaction using Spring’s integration support features, as shown in Listing 1:

Listing 1. Transaction rollback

@Transactional
  @Test
  public void testInsertIntoTwoDataSources(a) 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);

    // Data changes will be rolled back after this method exits

  }
Copy the code

MulipleDataSourceTests will then verify that both operations are rolled back, as shown in Listing 2:

Listing 2. Validate rollback

@AfterTransaction
  public void checkPostConditions(a) {

    int count = getJdbcTemplate().queryForInt("select count(*) from T_FOOS");
    // This data change has been rolled back by the test framework
    assertEquals(0, count);

    count = getOtherJdbcTemplate().queryForInt("select count(*) from T_AUDITS");
    // This data change is also rolled back due to XA
    assertEquals(0, count);

  }
Copy the code

To better understand how Spring transaction management works and how it is configured, refer to the Spring reference documentation.

XA optimized with 1PC

This pattern optimizes many transaction managers that contain only single-resource transactions by avoiding the performance overhead of 2PC. You’ll want your application services to solve this problem.

XA and final resource policy

Another feature of the XA transaction manager is that when XA is supported by all but one resource, it still provides the same data recovery guarantee as when XA is supported by all resources. This feature is implemented by ordering resources and involving non-XA resources in the decision. If the commit fails, all other resources are rolled back. This is almost a 100% guarantee of completeness, but it’s not perfect. When a commit fails, there is very little trace of an error unless additional measures are taken (and there are some high-end implementations).

Shared transaction resource pattern

In some systems, to reduce complexity and increase throughput, a good pattern is to eliminate XA dependency entirely by ensuring that all transactional resources in the system are actually different forms of the same resource. Obviously, this is impossible in all use cases, but this pattern is as reliable as XA, and often much faster. Such a shared transaction resource pattern is reliable enough, but it is limited to certain platforms and processing scenarios.

A simple example of this pattern that will be familiar to many is a Connection to a shared database between an object relational mapping (ORM) component and a JDBC component. This is what happens when you use Spring transaction managers that support ORM tools, such as Hibernate, EclipseLink, and the Java Persistence API (JPA). The same transaction can be safely used across ORM and JDBC components, and the execution process is typically implemented by service-level methods that control the transaction.

Another useful use of this pattern is message-driven updates to a single database, as shown in the simple example presented in this article. Message-oriented middleware systems need to store data somewhere, usually in a relational database. To implement this pattern, you simply specify that the target database for the messaging system is the same business database. This pattern requires vendors of messaging middleware to disclose details of their storage policies so that their configurations can point to the same database and hook into the same transaction.

Not all vendors can do this. Another approach that works for almost any database is to use Apache ActiveMQ for messaging and configure storage policies into the message broker server. Know the tricks, and it’s easy to configure. This configuration is shown in this article’s shared-JMS-DB sample project. The application’s code (in this case, unit tests) does not need to be aware of the use of this pattern because it is already enabled declaratively in the Spring configuration.

Example called SynchronousMessageTriggerAndRollbackTests unit tests to verify all the receiving synchronous message processing. TestReceiveMessageUpdateDatabase method receives the two messages, and insert the data records in the two messages to the database. When you exit this method, the test framework rolls back the current transaction, and you can verify that both messages and database updates have been rolled back, as shown in Listing 3:

Listing 3. Rollback of validation messages and database updates

@AfterTransaction
public void checkPostConditions(a) {

  assertEquals(0, SimpleJdbcTestUtils.countRowsInTable(jdbcTemplate, "T_FOOS"));
  List<String> list = getMessages();
  assertEquals(2, list.size());

}
Copy the code

The most important feature of this configuration is ActiveMQ’s persistence policy, which connects the message system of the business DataSource to the same DataSource, as well as the flag bit on the Spring JmsTemplate used to receive messages. The ActiveMQ persistence policy is configured as shown in Listing 4:

Listing 4. Persistent configuration of ActiveMQ

<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"
  depends-on="brokerService">
  <property vm://localhost?async=false" />
</bean>

<bean id="brokerService" class="org.apache.activemq.broker.BrokerService" init-method="start"
  destroy-method="stop">.<property >
    <bean class="org.apache.activemq.store.jdbc.JDBCPersistenceAdapter">
      <property >
        <bean class="com.springsource.open.jms.JmsTransactionAwareDataSourceProxy">
          <property />
          <property />
        </bean>
      </property>
      <property  />
    </bean>
  </property>
</bean>

Copy the code

The flag bit configuration on Spring JmsTemplate for receiving messages is shown in Listing 5:

Listing 5. Configure the transactionJmsTemplate

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">.<! -- This is important... -->
  <property  />
</bean>
Copy the code

Without sessionTransacted=true, API calls to JMS session transactions will never be executed and the receipt of messages will not be rolled back. The important point here is the special async=false parameter and the DataSource wrapper in the embedded message broker server, which together ensure that ActiveMQ and Spring use the same JDBC transaction Connection.

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.