The importance of @[TOC] transactions is self-evident, and Spring provides rich support for transactions, with a wide range of supported attributes.

However, as many of you know, there are two attributes that are particularly tricky:

  • Isolation,
  • disseminated

How convoluted is it? Songge has been too lazy to write an article to sum up. However, some friends recently asked this question, so I took some time to summarize. I will not explain the concept to you in a dry way, but Songko will demonstrate all the following content through specific cases.

All right, all right, look at the big screen.

1. What are transactions

A database transaction is a series of operations performed as a single logical unit of work that either succeeds or fails together as an indivisible unit of work.

In our daily work, there are many scenarios involving transactions. A service often needs to call different DAO layer methods, and these methods will either succeed or fail at the same time. We need to ensure this at the Service layer.

When it comes to transactions, the most typical example is transfer:

Sam wants to transfer 500 yuan to Li Si, there are two operations, subtract 500 yuan from Sam’s account, add 500 yuan to Li Si’s account, these two operations either succeed or fail at the same time, how to ensure that they succeed or fail at the same time? The answer is transactions.

Transactions have four properties (ACID) :

  • Atomicity: All operations in a transaction either complete or not complete, and do not end at some intermediate stage. If a transaction fails during execution, it will be rolled back to the state before the transaction began, as if the transaction had never been executed. That is, transactions are indivisible and irreducible.
  • Consistency: The integrity of the database is not compromised before and after a transaction. This means that the data written must fully comply with all preset constraints, triggers, cascading rollback, and so on.
  • Isolation: A database allows multiple concurrent transactions to read, write, and modify its data at the same time. Isolation prevents data inconsistency caused by cross execution when multiple transactions are executed concurrently. Transaction isolation can be divided into different levels, including Read Uncommitted, Read Committed, Repeatable Read, and Serializable.
  • Durability: Modifications to data are permanent after transactions end, they are not lost even if systems fail.

These are the four characteristics of transactions.

2. Transactions in Spring

2.1 Two usages

As an infrastructure for Java development, Spring also provides good support for transactions. In general, Spring supports two types of transactions, declarative and programmatic.

The programmatic transaction is similar to a Jdbc transaction method and need to embed the transaction code business logic, such code coupling is higher, the declarative transaction through the idea of AOP can effectively the transaction and business logic code decoupling, therefore, in the actual development declarative transaction has been widely used, while the programmatic transaction use fewer, For the sake of completeness, this article covers both transaction modes.

2.2 Three major infrastructures

Transaction support in Spring provides three major infrastructures, which we’ll start with.

  1. PlatformTransactionManager
  2. TransactionDefinition
  3. TransactionStatus

These three core classes are Spring’s core classes for handling transactions.

2.2.1 PlatformTransactionManager

PlatformTransactionManager is the core of transaction processing, it has many implementation class, as follows:

The PlatformTransactionManager are defined as follows:

public interface PlatformTransactionManager {
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition);
	void commit(TransactionStatus status) throws TransactionException;
	void rollback(TransactionStatus status) throws TransactionException;
}
Copy the code

Can see the PlatformTransactionManager defines the basic business operation method, operation method of these transactions are platform independent and specific implementation is achieved by different subclasses.

Just like JDBC, SUN sets the standard and other database vendors provide the implementation. The advantage of this is that we Java programmers only need to master the standard, not the implementation of the interface. PlatformTransactionManager, for example, it has many implementation, if you are using the JDBC DataSourceTransactionManager can be as a transaction manager; If you are using Hibernate, so can the HibernateTransactionManager as transaction manager; If you are using JPA, you can use JpaTransactionManager as the transaction manager. DataSourceTransactionManager, HibernateTransactionManager and JpaTransactionManager are PlatformTransactionManager concrete implementation, But we don’t need to grasp the usage of these specific implementation class, we only need to grasp the usage of good PlatformTransactionManager can.

In the PlatformTransactionManager mainly has the following three methods:

1.getTransaction()

GetTransaction () gets a transaction object based on the incoming TransactionDefinition, which defines some basic rules for a transaction, such as propagation, isolation level, and so on.

2.commit()

The commit() method is used to commit the transaction.

3.rollback()

The rollback() method uses the rollback transaction.

2.2.2 TransactionDefinition

TransactionDefinition describes the specific rules of a transaction, also known as transaction properties. What are the properties of a transaction? See below:

As you can see, there are five main attributes:

  1. Isolation,
  2. disseminated
  3. Rollback rules
  4. timeout
  5. Whether the read-only

These are the five properties that Songo will talk about in more detail.

The methods in the TransactionDefinition class are as follows:

You can see that there are five methods:

  1. GetIsolationLevel () to get the isolation level of the transaction
  2. GetName (), gets the transaction name
  3. GetPropagationBehavior (), obtains the transaction’s propagationBehavior
  4. GetTimeout (), get the transaction timeout time
  5. IsReadOnly (), which gets whether the transaction is read-only

TransactionDefinition also has a number of implementation classes, as follows:

If developers use the programmatic transaction, directly using DefaultTransactionDefinition can.

2.2.3 TransactionStatus

TransactionStatus can be directly understood as the transaction itself, the source code of this interface is as follows:

public interface TransactionStatus extends SavepointManager.Flushable {
	boolean isNewTransaction(a);
	boolean hasSavepoint(a);
	void setRollbackOnly(a);
	boolean isRollbackOnly(a);
	void flush(a);
	boolean isCompleted(a);
}
Copy the code
  1. The isNewTransaction() method gets whether the current transaction is a new transaction.
  2. The hasSavepoint() method determines whether savePoint() exists.
  3. The setRollbackOnly() method sets that the transaction must be rolled back.
  4. The isRollbackOnly() method gets transactions that can only be rolled back.
  5. The Flush () method flushs changes in the underlying session to the database. It is generally used for Hibernate/JPA sessions and has no effect on transactions such as JDBC-type transactions.
  6. The isCompleted() method is used to find whether a transaction isCompleted.

These are the three main infrastructure supporting transactions in Spring.

3. Programmatic transactions

Let’s first look at how programmatic transactions work.

Through the PlatformTransactionManager or TransactionTemplate can realize programmatic transaction. If you are in a Spring Boot project, Spring Boot will automatically provide these two objects, so we can use them directly. However, in a traditional SSM project, we would need to provide these two objects through configuration. Songo gives a simple configuration reference as follows (for simplicity, we use JdbcTemplate for database operations) :

<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql:///spring_tran? serverTimezone=Asia/Shanghai"/>
    <property name="username" value="root"/>
    <property name="password" value="123"/>
</bean>
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<bean class="org.springframework.transaction.support.TransactionTemplate" id="transactionTemplate">
    <property name="transactionManager" ref="transactionManager"/>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>
Copy the code

With these two objects, the following code is simple:

@Service
public class TransferService {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    PlatformTransactionManager txManager;

    public void transfer(a) {
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        TransactionStatus status = txManager.getTransaction(definition);
        try {
            jdbcTemplate.update("update user set account=account+100 where username='zhangsan'");
            int i = 1 / 0;
            jdbcTemplate.update("update user set account=account-100 where username='lisi'");
            txManager.commit(status);
        } catch(DataAccessException e) { e.printStackTrace(); txManager.rollback(status); }}}Copy the code

This code is very simple, there is nothing to explain, try… catch… Commit if there is no problem, rollback if there is a problem. If we need to configure the transaction isolation, transmission, etc., can be configured in the DefaultTransactionDefinition object.

The code above is achieved by PlatformTransactionManager programmatic transaction, we can also be accomplished by TransactionTemplate programmatic transaction, as follows:

@Service
public class TransferService {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    TransactionTemplate tranTemplate;
    public void transfer(a) {
        tranTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    jdbcTemplate.update("update user set account=account+100 where username='zhangsan'");
                    int i = 1 / 0;
                    jdbcTemplate.update("update user set account=account-100 where username='lisi'");
                } catch(DataAccessException e) { status.setRollbackOnly(); e.printStackTrace(); }}}); }}Copy the code

Inject the TransactionTemplate directly, then add a callback to the execute method to write the core business, and when an exception is thrown, mark the current transaction as rollback only. Note that the execute method, if you don’t need to get the results of the transaction, the direct use of TransactionCallbackWithoutResult class can, if you want to get the transaction execution result, using TransactionCallback can.

This is how the two types of programmatic transactions play out.

Programmatic transactions are so intrusive that we use declarative transactions more in our projects because they are rarely used in real development.

Declarative transactions

Declarative transactions can be non-intrusive if configured using XML. With a Java configuration, there is only one @Transactional annotation to hack into, which is relatively easy.

The following configuration is for traditional SSM projects (because in Spring Boot projects, transaction-related components are already configured) :

4.1 the XML configuration

XML configuration declarative transactions can be roughly divided into three steps, as follows:

  1. Configure the transaction manager
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql:///spring_tran? serverTimezone=Asia/Shanghai"/>
    <property name="username" value="root"/>
    <property name="password" value="123"/>
</bean>
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
Copy the code
  1. Configuring transaction Notification
<tx:advice transaction-manager="transactionManager" id="txAdvice">
    <tx:attributes>
        <tx:method name="m3"/>
        <tx:method name="m4"/>
    </tx:attributes>
</tx:advice>
Copy the code
  1. The configuration of AOP
<aop:config>
    <aop:pointcut id="pc1" expression="execution(* org.javaboy.demo.*.*(..) )"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pc1"/>
</aop:config>
Copy the code

The intersection of methods defined in steps 2 and 3 is the method to which we want to add the transaction.

Once configured, transactions are automatically available in the following methods:

public class UserService {
    public void m3(a){
        jdbcTemplate.update("update user set money=997 where username=?"."zhangsan"); }}Copy the code

4.2 Java configuration

We can also implement declarative transactions using Java configuration:

@Configuration
@ComponentScan
// Enable transaction annotation support
@EnableTransactionManagement
public class JavaConfig {
    @Bean
    DataSource dataSource(a) {
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setPassword("123");
        ds.setUsername("root");
        ds.setUrl("jdbc:mysql:///test01? serverTimezone=Asia/Shanghai");
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return ds;
    }

    @Bean
    JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    PlatformTransactionManager transactionManager(a) {
        return newDataSourceTransactionManager(dataSource()); }}Copy the code

The configuration here is similar to the configuration in XML, but there are only two key ones:

  • The transaction manager PlatformTransactionManager.
  • @ EnableTransactionManagement annotation open transaction support.

Once configured, you can then add the @Transactional annotation to any method that requires a transaction, as follows:

@Transactional(noRollbackFor = ArithmeticException.class)
public void update4(a) {
    jdbcTemplate.update("update account set money = ? where username=? ;".998."lisi");
    int i = 1 / 0;
}
Copy the code

Of course, this is a bit of code intrusion, but it’s not a problem, and it’s used a lot in everyday development. When the @Transactional annotation is applied to a class, it means that all methods of that class have transactions. When the annotation is applied to a method, it means that that method has transactions.

4.3 Mixed Configuration

Declarative transactions can also be implemented with a mixture of Java code and XML configuration, with some configuration implemented in XML and some configuration implemented in Java code:

Assume the XML configuration is as follows:


      
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <! Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional
    <tx:annotation-driven />

</beans>
Copy the code

The configuration in the Java code is as follows:

@Configuration
@ComponentScan
@ImportResource(locations = "classpath:applicationContext3.xml")
public class JavaConfig {
    @Bean
    DataSource dataSource(a) {
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setPassword("123");
        ds.setUsername("root");
        ds.setUrl("jdbc:mysql:///test01? serverTimezone=Asia/Shanghai");
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return ds;
    }

    @Bean
    JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    PlatformTransactionManager transactionManager(a) {
        return newDataSourceTransactionManager(dataSource()); }}Copy the code

The Java configuration uses the @ImportResource annotation to import the XML configuration that enables the @Transactional annotation. So the Java configuration omits the @ EnableTransactionManagement annotation.

These are some of the ways declarative transactions can be configured. Fun!

5. Transaction properties

In the previous configuration, we only talked about the use of transactions and did not talk about the details of some of the properties of a transaction, so let’s take a closer look at the five properties of a transaction.

5.1 isolation,

The first is the isolation of the transaction, which is the isolation level of the transaction.

There are four different isolation levels in MySQL, all of which are well supported in Spring. The default transaction isolation level in Spring is default, which means that the isolation level of the database itself is whatever it is, and default can satisfy most of our daily development scenarios.

However, we can adjust the isolation level of the transaction if the project requires it.

The adjustment mode is as follows:

5.1.1 Programmatic transaction isolation level

If it is a programmatic transaction, change the isolation level of the transaction as follows:

TransactionTemplate

transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
Copy the code

The various isolation levels are defined in TransactionDefinition.

PlatformTransactionManager

public void update2(a) {
    // Create the default configuration for the transaction
    DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
    definition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
    TransactionStatus status = platformTransactionManager.getTransaction(definition);
    try {
        jdbcTemplate.update("update account set money = ? where username=? ;".999."zhangsan");
        int i = 1 / 0;
        // Commit the transaction
        platformTransactionManager.commit(status);
    } catch (DataAccessException e) {
          e.printStackTrace();
        / / rollbackplatformTransactionManager.rollback(status); }}Copy the code

This is in DefaultTransactionDefinition object set the transaction isolation level.

5.1.2 Declarative transaction isolation level

If it is a declarative transaction change the isolation level as follows:

XML:

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <! Add transaction -->
        <tx:method name="add*"/>
        <tx:method name="insert*" isolation="SERIALIZABLE"/>
    </tx:attributes>
</tx:advice>
Copy the code

Java:

@Transactional(isolation = Isolation.SERIALIZABLE)
public void update4(a) {
    jdbcTemplate.update("update account set money = ? where username=? ;".998."lisi");
    int i = 1 / 0;
}
Copy the code

If you are not familiar with the isolation level of a transaction, you can refer to Songo’s previous article: Four Cases to understand MySQL Transaction Isolation Level.

5.2 transmitted

Let’s start with what is the transmissibility of transactions:

Transaction propagation behavior is to solve the transaction problem that methods in the business layer call each other. When a transaction method is called by another transaction method, what state should the transaction exist? For example, a new method may continue to run in an existing transaction, may start a new transaction and run in its own transaction, etc. These rules relate to the transmissibility of a transaction.

In terms of transactionality, Spring defines the following:

public enum Propagation {
	REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
	SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
	MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
	REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
	NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
	NEVER(TransactionDefinition.PROPAGATION_NEVER),
	NESTED(TransactionDefinition.PROPAGATION_NESTED);
	private final int value;
	Propagation(int value) { this.value = value; }
	public int value(a) { return this.value; }}Copy the code

The specific meanings are as follows:

disseminated describe
REQUIRED If a transaction exists, join the transaction. If there is no transaction currently, a new transaction is created
SUPPORTS If a transaction exists, join the transaction. If there is no transaction currently, it continues in a non-transactional manner
MANDATORY If a transaction exists, join the transaction. If there is no transaction currently, an exception is thrown
REQUIRES_NEW Creates a new transaction and suspends the current transaction if one exists
NOT_SUPPORTED Runs nontransactionally and suspends the current transaction if one exists
NEVER Runs nontransactionally and throws an exception if a transaction currently exists
NESTED If a transaction exists, a transaction is created to run as a nested transaction of the current transaction; If no current affairs, the value of equivalent to the TransactionDefinition. PROPAGATION_REQUIRED

There are seven types of transmission, and the specific configuration is simple:

Configuration in TransactionTemplate

transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
Copy the code

Configuration in the PlatformTransactionManager

public void update2(a) {
    // Create the default configuration for the transaction
    DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
    definition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
    definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    TransactionStatus status = platformTransactionManager.getTransaction(definition);
    try {
        jdbcTemplate.update("update account set money = ? where username=? ;".999."zhangsan");
        int i = 1 / 0;
        // Commit the transaction
        platformTransactionManager.commit(status);
    } catch (DataAccessException e) {
          e.printStackTrace();
        / / rollbackplatformTransactionManager.rollback(status); }}Copy the code

Configuration of declarative transactions (XML)

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <! Add transaction -->
        <tx:method name="add*"/>
        <tx:method name="insert*" isolation="SERIALIZABLE" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>
Copy the code

Configuration of declarative transactions (Java)

@Transactional(noRollbackFor = ArithmeticException.class,propagation = Propagation.REQUIRED)
public void update4(a) {
    jdbcTemplate.update("update account set money = ? where username=? ;".998."lisi");
    int i = 1 / 0;
}
Copy the code

Use is used in this way, as for the seven transmission of the specific meaning, Songko and we say one by one.

5.2.1 the REQUIRED

REQUIRED indicates joining a transaction if it currently exists. If there is no transaction currently, a new transaction is created.

For example, I have the following code:

@Service
public class AccountService {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Transactional
    public void handle1(a) {
        jdbcTemplate.update("update user set money = ? where id=? ;".1.2); }}@Service
public class AccountService2 {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    AccountService accountService;
    public void handle2(a) {
        jdbcTemplate.update("update user set money = ? where username=? ;".1."zhangsan"); accountService.handle1(); }}Copy the code

I call handle1 in the handle2 method.

So:

  1. If the handle2 method itself has a transaction, the Handle1 method is added to the transaction in which the Handle2 method is located, so that both methods succeed or fail together in the same transaction (either Handle2 or Handle1, who throws an exception, Will result in an overall rollback).
  2. If the Handle2 method itself has no transactions, then the Handle1 method starts a new transaction and plays by itself.

As a simple example, the handle2 method has a transaction and the handle1 method has a transaction. The project prints the transaction log as follows:

o.s.jdbc.support.JdbcTransactionManager : Creating new transaction with name [org.javaboy.spring_tran02.AccountService2.handle2]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT o.s.jdbc.support.JdbcTransactionManager : Acquired Connection [HikariProxyConnection@875256468 wrapping com.mysql.cj.jdbc.ConnectionImpl@9753d50] for JDBC transaction o.s.jdbc.support.JdbcTransactionManager : Switching JDBC Connection [HikariProxyConnection@875256468 wrapping com.mysql.cj.jdbc.ConnectionImpl@9753d50] to manual commit o.s.jdbc.core.JdbcTemplate : Executing prepared SQL update o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [update user set money = ? where username=?;]  o.s.jdbc.support.JdbcTransactionManager : Participating in existing transaction o.s.jdbc.core.JdbcTemplate : Executing prepared SQL update o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [update user set money = ? where id=?;]  o.s.jdbc.support.JdbcTransactionManager : Initiating transaction commit o.s.jdbc.support.JdbcTransactionManager : Committing JDBC transaction on Connection [HikariProxyConnection@875256468 wrapping com.mysql.cj.jdbc.ConnectionImpl@9753d50] o.s.jdbc.support.JdbcTransactionManager : Releasing JDBC Connection [HikariProxyConnection@875256468 wrapping com.mysql.cj.jdbc.ConnectionImpl@9753d50] after transactionCopy the code

As can be seen from the log, a total of one transaction has been started, the log has such a sentence:

Participating in existing transaction
Copy the code

This means that the Handle1 method does not start the transaction itself, but joins the handle2 method in the transaction.

5.2.2 the REQUIRES_NEW

REQUIRES_NEW creates a new transaction and suspends the current transaction if one exists. In other words, REQUIRES_NEW starts its own transaction whether or not the external method has one.

“REQUIRES_NEW” and “REQUIRED” are so similar that there seems to be no difference. In fact, if you just look at the final rollback effect, you probably won’t see much difference. However, note the bold in Songo. In REQUIRES_NEW, it is possible to have two transactions at the same time, with the external method’s transaction suspended and the internal method’s transaction running alone, whereas in REQUIRED, this is not the case. If both internal and external methods are propagated REQUIRED, In the end, it’s just a transaction.

Again, assuming that both handle1 and Handle2 methods have transactions, handle2 has a REQUIRED transaction propagation and Handle1 has a REQUIRES_NEW transaction propagation, the final printed transaction log would look like this:

o.s.jdbc.support.JdbcTransactionManager : Creating new transaction with name [org.javaboy.spring_tran02.AccountService2.handle2]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT o.s.jdbc.support.JdbcTransactionManager : Acquired Connection [HikariProxyConnection@422278016 wrapping com.mysql.cj.jdbc.ConnectionImpl@732405c2] for JDBC transaction o.s.jdbc.support.JdbcTransactionManager : Switching JDBC Connection [HikariProxyConnection@422278016 wrapping com.mysql.cj.jdbc.ConnectionImpl@732405c2] to manual  commit o.s.jdbc.core.JdbcTemplate : Executing prepared SQL update o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [update user set money = ? where username=?;]  o.s.jdbc.support.JdbcTransactionManager : Suspending current transaction, creating new transaction with name [org.javaboy.spring_tran02.AccountService.handle1] o.s.jdbc.support.JdbcTransactionManager : Acquired Connection [HikariProxyConnection@247691344 wrapping com.mysql.cj.jdbc.ConnectionImpl@14ad4b95] for JDBC transaction com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@14ad4b95 o.s.jdbc.support.JdbcTransactionManager : Switching JDBC Connection [HikariProxyConnection@247691344 wrapping com.mysql.cj.jdbc.ConnectionImpl@14ad4b95] to manual  commit o.s.jdbc.core.JdbcTemplate : Executing prepared SQL update o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [update user set money = ? where id=?;]  o.s.jdbc.support.JdbcTransactionManager : Initiating transaction commit o.s.jdbc.support.JdbcTransactionManager : Committing JDBC transaction on Connection [HikariProxyConnection@247691344 wrapping com.mysql.cj.jdbc.ConnectionImpl@14ad4b95] o.s.jdbc.support.JdbcTransactionManager : Releasing JDBC Connection [HikariProxyConnection@247691344 wrapping com.mysql.cj.jdbc.ConnectionImpl@14ad4b95] after transaction o.s.jdbc.support.JdbcTransactionManager : Resuming suspended transaction after completion of inner transaction o.s.jdbc.support.JdbcTransactionManager : Initiating transaction commit o.s.jdbc.support.JdbcTransactionManager : Committing JDBC transaction on Connection [HikariProxyConnection@422278016 wrapping com.mysql.cj.jdbc.ConnectionImpl@732405c2] o.s.jdbc.support.JdbcTransactionManager : Releasing JDBC Connection [HikariProxyConnection@422278016 wrapping com.mysql.cj.jdbc.ConnectionImpl@732405c2] after transactionCopy the code

Analyzing this log, we can see:

  1. First, a transaction is opened for the Handle2 method.
  2. There is a corruption that follows the execution of SQL in handle2.
  3. A new transaction is started for the handle1 method.
  4. SQL to execute the handle1 method.
  5. Commit the transaction for the handle1 method.
  6. Restores suspended transactions (escalations).
  7. Commit transactions for the handle2 method.

From this log you can see the distinction between REQUIRES_NEW and REQUIRED very clearly.

Songo briefly concludes (assuming that the transaction propagation of the handle1 method is REQUIRES_NEW) :

  1. If the Handle2 method does not have a transaction, the Handle1 method opens a transaction and plays by itself.
  2. If the Handle2 method has a transaction, the Handle1 method still starts a transaction. At this point, if handle2 is rolled back with an exception, it does not cause the handle1 method to be rolled back because handle1 is a separate transaction; Handle1 can also be rolled back if an exception occurs in the handle1 method and the exception is not passed to the Handle2 method by catching processing.

InnoDB will use table locks if the query field is not an index field. This will cause handle1 to wait. Handle1 must complete before handle2 can release the lock. So, in the above test, we set the username field to the index field so that row locks are used by default.

5.2.3 requires NESTED

NESTED indicates that if a transaction exists, a transaction is created to run as a NESTED transaction of the current transaction. If no current affairs, the value of equivalent to the TransactionDefinition. PROPAGATION_REQUIRED.

Given that the handle2 method has a transaction and the handle1 method also has a transaction and is propagated, the final transaction log is executed as follows:

o.s.jdbc.support.JdbcTransactionManager : Creating new transaction with name [org.javaboy.demo.AccountService2.handle2]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT o.s.jdbc.support.JdbcTransactionManager : Acquired Connection [HikariProxyConnection@2025689131 wrapping com.mysql.cj.jdbc.ConnectionImpl@2ed3628e] for JDBC transaction o.s.jdbc.support.JdbcTransactionManager : Switching JDBC Connection [HikariProxyConnection@2025689131 wrapping com.mysql.cj.jdbc.ConnectionImpl@2ed3628e] to manual commit o.s.jdbc.core.JdbcTemplate : Executing prepared SQL update o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [update user set money = ? where username=?;]  o.s.jdbc.support.JdbcTransactionManager : Creating nested transaction with name [org.javaboy.demo.AccountService.handle1] o.s.jdbc.core.JdbcTemplate : Executing prepared SQL update o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [update user set money = ? where id=?;]  o.s.jdbc.support.JdbcTransactionManager : Releasing transaction savepoint o.s.jdbc.support.JdbcTransactionManager : Initiating transaction commit o.s.jdbc.support.JdbcTransactionManager : Committing JDBC transaction on Connection [HikariProxyConnection@2025689131 wrapping com.mysql.cj.jdbc.ConnectionImpl@2ed3628e] o.s.jdbc.support.JdbcTransactionManager : Releasing JDBC Connection [HikariProxyConnection@2025689131 wrapping com.mysql.cj.jdbc.ConnectionImpl@2ed3628e] after transactionCopy the code

The key sentence in Creating nested transaction.

At this point, the NESTED internal method (handle1) belongs to a subtransaction of an external transaction. If the external main transaction is rolled back, the subtransaction is also rolled back, while the internal subtransaction can be rolled back independently without affecting the external main transaction and other subtransactions (exceptions of the internal subtransaction need to be handled).

5.2.4 MANDATORY

MANDATORY Indicates that a transaction is added if it exists. If there is no transaction currently, an exception is thrown.

This is easy to understand. Let me give you two examples:

Given that the handle2 method has transactions and the handle1 method has transactions and the propagation is MANDATORY, the final transaction log is executed as follows:

o.s.jdbc.support.JdbcTransactionManager : Creating new transaction with name [org.javaboy.demo.AccountService2.handle2]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT o.s.jdbc.support.JdbcTransactionManager : Acquired Connection [HikariProxyConnection@768820610 wrapping com.mysql.cj.jdbc.ConnectionImpl@14840df2] for JDBC transaction o.s.jdbc.support.JdbcTransactionManager : Switching JDBC Connection [HikariProxyConnection@768820610 wrapping com.mysql.cj.jdbc.ConnectionImpl@14840df2] to manual  commit o.s.jdbc.core.JdbcTemplate : Executing prepared SQL update o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [update user set money = ? where username=?;]  o.s.jdbc.support.JdbcTransactionManager : Participating in existing transaction o.s.jdbc.core.JdbcTemplate : Executing prepared SQL update o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [update user set money = ? where id=?;]  o.s.jdbc.support.JdbcTransactionManager : Initiating transaction commit o.s.jdbc.support.JdbcTransactionManager : Committing JDBC transaction on Connection [HikariProxyConnection@768820610 wrapping com.mysql.cj.jdbc.ConnectionImpl@14840df2] o.s.jdbc.support.JdbcTransactionManager : Releasing JDBC Connection [HikariProxyConnection@768820610 wrapping com.mysql.cj.jdbc.ConnectionImpl@14840df2] after transactionCopy the code

As you can see from this log:

  1. Start the transaction for the handle2 method.
  2. SQL to execute the handle2 method.
  3. The handle1 method is added to an existing transaction.
  4. SQL to execute the handle1 method.
  5. Commit the transaction.

If the handle2 method has no transactions and the handle1 method has transactions and the propagation is MANDATORY, the following exception will be thrown:

No existing transaction found for transaction marked with propagation 'mandatory'
Copy the code

There was an error because there were no existing transactions.

5.2.5 SUPPORTS

SUPPORTS indicates that a transaction will be joined if it currently exists. If there is no transaction currently, it continues in a non-transactional manner.

This one is easy. I’ll give you two examples.

Given that the Handle2 method has transactions and the Handle1 method has transactions with SUPPORTS propagation, the final transaction execution log is as follows:

o.s.jdbc.support.JdbcTransactionManager : Creating new transaction with name [org.javaboy.demo.AccountService2.handle2]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT o.s.jdbc.support.JdbcTransactionManager : Acquired Connection [HikariProxyConnection@1780573324 wrapping com.mysql.cj.jdbc.ConnectionImpl@44eafcbc] for JDBC transaction o.s.jdbc.support.JdbcTransactionManager : Switching JDBC Connection [HikariProxyConnection@1780573324 wrapping com.mysql.cj.jdbc.ConnectionImpl@44eafcbc] to manual commit o.s.jdbc.core.JdbcTemplate : Executing prepared SQL update o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [update user set money = ? where username=?;]  o.s.jdbc.support.JdbcTransactionManager : Participating in existing transaction o.s.jdbc.core.JdbcTemplate : Executing prepared SQL update o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [update user set money = ? where id=?;]  o.s.jdbc.support.JdbcTransactionManager : Initiating transaction commit o.s.jdbc.support.JdbcTransactionManager : Committing JDBC transaction on Connection [HikariProxyConnection@1780573324 wrapping com.mysql.cj.jdbc.ConnectionImpl@44eafcbc] o.s.jdbc.support.JdbcTransactionManager : Releasing JDBC Connection [HikariProxyConnection@1780573324 wrapping com.mysql.cj.jdbc.ConnectionImpl@44eafcbc] after transactionCopy the code

The log is simple, there is nothing to talk about. Looking for Participating in existing transaction means joining the existing transaction.

If the handle2 method has no transactions and the Handle1 method has transactions and SUPPORTS propagation, this will eventually not start a transaction and will not log.

5.2.6 NOT_SUPPORTED

NOT_SUPPORTED means to run non-transactionally, suspending the current transaction if one exists.

If the handle2 method has transactions and the handle1 method has transactions and the propagation is NOT_SUPPORTED, then the final transaction execution log is as follows:

o.s.jdbc.support.JdbcTransactionManager : Creating new transaction with name [org.javaboy.demo.AccountService2.handle2]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT o.s.jdbc.support.JdbcTransactionManager : Acquired Connection [HikariProxyConnection@1365886554 wrapping com.mysql.cj.jdbc.ConnectionImpl@3198938b] for JDBC transaction o.s.jdbc.support.JdbcTransactionManager : Switching JDBC Connection [HikariProxyConnection@1365886554 wrapping com.mysql.cj.jdbc.ConnectionImpl@3198938b] to manual commit o.s.jdbc.core.JdbcTemplate : Executing prepared SQL update o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [update user set money = ? where username=?;]  o.s.jdbc.support.JdbcTransactionManager : Suspending current transaction o.s.jdbc.core.JdbcTemplate : Executing prepared SQL update o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [update user set money = ? where id=?;]  o.s.jdbc.datasource.DataSourceUtils : Fetching JDBC Connection from DataSource o.s.jdbc.support.JdbcTransactionManager : Resuming suspended transaction after completion of inner transaction o.s.jdbc.support.JdbcTransactionManager : Initiating transaction commit o.s.jdbc.support.JdbcTransactionManager : Committing JDBC transaction on Connection [HikariProxyConnection@1365886554 wrapping com.mysql.cj.jdbc.ConnectionImpl@3198938b] o.s.jdbc.support.JdbcTransactionManager : Releasing JDBC Connection [HikariProxyConnection@1365886554 wrapping com.mysql.cj.jdbc.ConnectionImpl@3198938b] after transactionCopy the code

Suspending current transaction refers to Suspending a current transaction; A suspended suspended Transaction represents the recovery of a suspended transaction.

5.2.7 NEVER

NEVER means to run non-transactionally and throw an exception if a transaction currently exists.

If the handle2 method has a transaction and the handle1 method has a transaction with propagation of NEVER, the following exception will eventually be thrown:

Existing transaction found for transaction marked with propagation 'never'
Copy the code

5.3 Rollback Rules

By default, transactions are rolled back only when they encounter run-time exceptions (subclasses of RuntimeException) and Error, and not when they encounter Checked exceptions.

Things like 1/0, null Pointers are runtimeExceptions, IOException is Checked Exception, in other words, by default, IOException does not cause the transaction to roll back if it occurs.

If we want to trigger transaction rollback when IOException occurs, we can configure it as follows:

The Java configuration:

@Transactional(rollbackFor = IOException.class)
public void handle2(a) {
    jdbcTemplate.update("update user set money = ? where username=? ;".1."zhangsan");
    accountService.handle1();
}
Copy the code

The XML configuration:

<tx:advice transaction-manager="transactionManager" id="txAdvice">
    <tx:attributes>
        <tx:method name="m3" rollback-for="java.io.IOException"/>
    </tx:attributes>
</tx:advice>
Copy the code

Alternatively, we can specify not to roll back certain exceptions, such as not to trigger transaction rollback when the system throws ArithmeticException, as follows:

The Java configuration:

@Transactional(noRollbackFor = ArithmeticException.class)
public void handle2(a) {
    jdbcTemplate.update("update user set money = ? where username=? ;".1."zhangsan");
    accountService.handle1();
}
Copy the code

The XML configuration:

<tx:advice transaction-manager="transactionManager" id="txAdvice">
    <tx:attributes>
        <tx:method name="m3" no-rollback-for="java.lang.ArithmeticException"/>
    </tx:attributes>
</tx:advice>
Copy the code

5.4 Read-only

Read-only transactions are typically set up on query methods, but not all query methods require read-only transactions, depending on the situation.

In general, if the business method has only one query SQL, there is no need to add a transaction, forcing it will end up being counterproductive.

However, if there are multiple query SQL in a business method, the situation is different: multiple query SQL, by default, each query SQL will open a separate transaction, so that if a concurrent operation changes the data, multiple query SQL will look up different data. At this point, if we enable the transaction and set it to read-only, multiple SQL queries will be placed in the same transaction, and multiple SQL queries executed in this transaction will get the same query results.

Set transaction read-only as follows:

The Java configuration:

@Transactional(readOnly = true)
Copy the code

The XML configuration:

<tx:advice transaction-manager="transactionManager" id="txAdvice">
    <tx:attributes>
        <tx:method name="m3" read-only="true"/>
    </tx:attributes>
</tx:advice>
Copy the code

5.5 Timeout Period

Timeout is the maximum time that a transaction is allowed to execute, and if the transaction has not completed after this time, the transaction is automatically rolled back.

The transaction timeout period can be configured as follows (in seconds) :

The Java configuration:

@Transactional(timeout = 10)
Copy the code

The XML configuration:

<tx:advice transaction-manager="transactionManager" id="txAdvice">
    <tx:attributes>
        <tx:method name="m3" read-only="true" timeout="10"/>
    </tx:attributes>
</tx:advice>
Copy the code

The timeout period in TransactionDefinition is represented by an int in seconds. The default value is -1.

6. Precautions

  1. Transactions are valid only when applied to public methods.
  2. Transactions need to be invoked from outside, and Spring self-tuning transactions will fail. If method A calls method B, the transaction of method B will be invalid. This is especially important because the proxy mode only intercepts external method calls passed in through the proxy, so self-calling transactions are not effective.
  3. It is recommended that the Transactional annotation @Transactional be added to an implementation class rather than defined on an interface. If applied to an interface class or interface method, this annotation will only be applied when configuring an interface-based proxy.

7. Summary

Ok, so this is how Spring transactions play. I don’t know if you understand? The accompanying video has been recorded and uploaded, please look forward to it