Spring - understand spring transactions, isolation and propagation

Posted by longtone on Thu, 28 Oct 2021 21:51:12 +0200

The importance of transactions is self-evident. Spring also provides very rich support for transactions, with all kinds of supported properties.

However, many small partners know that there are two properties:

  • Isolation

  • Transmissibility

1. What is a transaction

Database transaction refers to a series of operations performed as a single logical unit of work. These operations either succeed or fail together. It is an inseparable unit of work.

In our daily work, there are many scenarios involving transactions. A service often needs to call different dao layer methods. These methods 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 case is transfer:

Zhang San wants to transfer 500 yuan to Li Si, which involves two operations: subtracting 500 yuan from Zhang San's account and adding 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 business.

Transactions have four characteristics (ACID):

  • Atomicity:   All operations in a transaction are either completed or not completed, and will not end in an intermediate link. If an error occurs during the execution of a transaction, it will be rolled back to the state before the start of the transaction, as if the transaction had never been executed. That is, transactions are indivisible and irreducible.

  • Consistency:   Before and after the transaction, the integrity of the database is not destroyed. This means that the written data must fully comply with all preset constraints, triggers, cascading rollback, etc.

  • Isolation:   The database allows multiple concurrent transactions to read, write and modify their data at the same time. Isolation can prevent data inconsistency caused by cross execution when multiple transactions are executed concurrently. Transaction isolation is divided into different levels, including Read Uncommitted, Read Committed, Repeatable Read, and Serializable.

  • Durability:   After the transaction is completed, the data modification is permanent and will not be lost even if the system fails.

These are the four characteristics of transactions.

2. Transactions in Spring

2.1. Two usages

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

Programming transaction is similar to the writing method of Jdbc transaction. It needs to embed the transaction code into the business logic, so the coupling degree of the code is high. While declarative transaction can effectively decouple the transaction and business logic code through the idea of AOP. Therefore, declarative transaction is widely used in practical development, while programming transaction is less used, Considering the integrity of the article, this article will introduce both transaction methods.

2.2. Three infrastructures

The transaction support in Spring provides three infrastructures. Let's learn about them first.

  1. PlatformTransactionManager

  2. TransactionDefinition

  3. TransactionStatus

These three core classes are the core classes that Spring handles transactions.

2.2.1,PlatformTransactionManager

PlatformTransactionManager is the core of transaction processing. It has many implementation classes, as follows:

PlatformTransactionManager is defined as follows:

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

Can see   PlatformTransactionManager   The basic transaction operation methods are defined in. These transaction operation methods are platform independent, and the specific implementation is implemented by different subclasses.

Just like JDBC, SUN sets standards and other database vendors provide specific implementations. The advantage of doing so is that we Java programmers only need to master this set of standards, and don't have to worry about the specific implementation of the interface. with   PlatformTransactionManager   For example, it has many implementations. If you use JDBC, you can   DataSourceTransactionManager   As a transaction manager; If you are using Hibernate, you can   HibernateTransactionManager   As a transaction manager; If you are using JPA, you can   JpaTransactionManager   As a transaction manager. DataSourceTransactionManager,HibernateTransactionManager   as well as   JpaTransactionManager   All   PlatformTransactionManager   But we don't need to master the usage of these concrete implementation classes, we just need to master them   PlatformTransactionManager   Just use.

PlatformTransactionManager   There are three main methods:

  1. getTransaction()
    getTransaction() obtains a transaction object according to the passed in TransactionDefinition, which defines some basic rules of transactions, such as propagation, isolation level, etc.
  2. commit()
    The commit() method is used to commit the transaction.
  3. rollback()
    The rollback() method is used to roll back the transaction.

2.2.2,TransactionDefinition

TransactionDefinition   The specific rules used to describe transactions are also called transaction attributes. What are the attributes of a transaction? See the figure below:

As you can see, there are five main attributes:

  1. Isolation

  2. Transmissibility

  3. Rollback rule

  4. Timeout

  5. Read only

These five attributes will be introduced in detail.

TransactionDefinition   The methods in the class are as follows:

  You can see that there are five methods:

  1. getIsolationLevel(), get the isolation level of the transaction

  2. getName(), get the name of the transaction

  3. getPropagationBehavior(), get the propagativity of the transaction

  4. getTimeout(), get the timeout of the transaction

  5. isReadOnly(), gets whether the transaction is read-only

TransactionDefinition also has many implementation classes, as follows:

If developers use programmatic transactions, use them directly   DefaultTransactionDefinition   Just.

2.2.3,TransactionStatus

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

public interface TransactionStatus extends SavepointManager, Flushable {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    void flush();
    boolean isCompleted();
}
  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 a transaction, which can only be rolled back.

  5. The flush() method refreshes the modifications in the underlying session to the database. It is generally used in Hibernate/JPA sessions and has no impact on transactions such as JDBC.

  6. The isCompleted() method is used to obtain whether a transaction ends.

These are the three infrastructure supporting transactions in Spring.

3. Programming transaction

Let's see how programmatic transactions play.

Programmatic transactions can be implemented through platform transaction manager or transaction template. If you are in a Spring Boot project, these two objects will be automatically provided by Spring Boot, and we can use them directly. However, in a traditional SSM project, we need to provide these two objects through configuration. SongGe gives a simple configuration reference as follows (for simplicity, we use JdbcTemplate for database operation):

<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>

With these two objects, the following code is simple:

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

    public void transfer() {
        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);
        }
    }
}

This code is very simple, there is nothing to explain, in   try...catch...   For business operations, commit if there is no problem, and rollback if there is a problem. If we need to configure the isolation and propagation of transactions, we can configure them in the DefaultTransactionDefinition object.

The above code is a programmatic transaction implemented through PlatformTransactionManager. We can also implement a programmatic transaction through TransactionTemplate, as follows:

@Service
public class TransferService {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    TransactionTemplate tranTemplate;
    public void transfer() {
        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();
                }
            }
        });
    }
}

Directly inject the TransactionTemplate, and then add a callback to write the core business in the execute method. When an exception is thrown, mark the current transaction as rollback only. Note that in the execute method, if you do not need to obtain the transaction execution result, you can directly use the TransactionCallback withoutresult class. If you want to obtain the transaction execution result, you can use TransactionCallback.

These are two ways to play programmatic transactions.

Programming transactions are too serious due to code intrusion, because they are rarely used in actual development. We use declarative transactions more in our projects.

4. Declarative transaction

Declarative transactions, if used   XML   Configuration, no intrusion can be achieved; If used   Java   Configuration, there is only one  @ Transactional   It's just annotation intrusion. It's relatively easy.

The following configurations are for traditional SSM projects (because in the Spring Boot project, transaction related components have been configured):

4.1. XML configuration

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

  1. Configure 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>
  2. Configure transaction notifications

    <tx:advice transaction-manager="transactionManager" id="txAdvice">
        <tx:attributes>
            <tx:method name="m3"/>
            <tx:method name="m4"/>
        </tx:attributes>
    </tx:advice>
  3. Configure AOP

    <aop:config>
        <aop:pointcut id="pc1" expression="execution(* org.javaboy.demo.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pc1"/>
    </aop:config>

The intersection of the methods defined in step 2 and step 3 is the method we want to add transactions.

After configuration, the following methods will automatically have transactions:

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

  4.2 Java configuration

We can also use Java configuration to implement declarative transactions:

@Configuration
@ComponentScan
//Enable transaction annotation support
@EnableTransactionManagement
public class JavaConfig {
    @Bean
    DataSource dataSource() {
        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() {
        return new DataSourceTransactionManager(dataSource());
    }
}

In fact, the things to be configured here are similar to those configured in XML, and the two most critical ones are:

  • Transaction manager PlatformTransactionManager.

  • @The enable transaction management annotation enables transaction support.

After the configuration is completed, the transaction is added on which method needs transaction  @ Transactional   Just annotate as follows:

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

Of course, this is a little bit of code intrusion, but it's not a big problem. This method is used more in daily development. When @ Transactional   When an annotation is added to a class, it means that all methods of the class have transactions. When the annotation is added to a method, it means that the method has transactions.

4.3. Mixed configuration

Declarative transactions can also be realized by mixed configuration of Java code and XML, that is, some configurations are realized by XML and some configurations are realized by java code:

Assume that the XML configuration is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<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">

    <!--
    Open the annotation configuration of the transaction. After adding this configuration, you can directly use it in the code @Transactional Annotation to open the transaction
    -->
    <tx:annotation-driven />

</beans>

The configuration in the Java code is as follows:

@Configuration
@ComponentScan
@ImportResource(locations = "classpath:applicationContext3.xml")
public class JavaConfig {
    @Bean
    DataSource dataSource() {
        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() {
        return new DataSourceTransactionManager(dataSource());
    }
}

In the Java configuration, the XML configuration is imported through the @ ImportResource annotation, and the content in the XML configuration is enabled  @ Transactional   Annotation, so the @ EnableTransactionManagement annotation is omitted from the Java configuration.

This is how declarative transactions are configured. Have fun!

5. Transaction properties

In the previous configuration, we just briefly talked about the usage of transactions and didn't talk about some attribute details of transactions with you in detail. Next, let's carefully stroke the five attributes of transactions.

5.1 isolation

The first is the isolation of transactions, that is, the isolation level of transactions.

There are four different isolation levels in MySQL, which are well supported in Spring. The default transaction isolation level in Spring is default, that is, the isolation level of the database itself is what it is. Default can meet most scenarios in our daily development.

However, we can also adjust the isolation level of transactions if necessary.

The adjustment method is as follows:

5.1.1. Programming transaction isolation level

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

TransactionTemplate

transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);

Various isolation levels are defined in the transaction definition.

PlatformTransactionManager

public void update2() {
    //Create default configuration for transactions
    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 transaction
        platformTransactionManager.commit(status);
    } catch (DataAccessException e) {
          e.printStackTrace();
        //RollBACK 
        platformTransactionManager.rollback(status);
    }
}

Here is to set the isolation level of transactions in the DefaultTransactionDefinition object.

5.1.2 declarative transaction isolation level

For declarative transactions, modify the isolation level as follows:

XML:

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!--with add Start method, add transaction-->
        <tx:method name="add*"/>
        <tx:method name="insert*" isolation="SERIALIZABLE"/>
    </tx:attributes>
</tx:advice>

Java:

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

5.2 communication

First, let's talk about the communication of affairs:

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

As for transaction propagation, Spring mainly defines the following types:

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() { return this.value; }
}

The specific meanings are as follows:

Transmissibilitydescribe
REQUIREDIf a transaction currently exists, join the transaction; If there is no current transaction, a new transaction is created
SUPPORTSIf a transaction currently exists, join the transaction; If there are currently no transactions, continue to run in a non transactional manner
MANDATORYIf a transaction currently exists, join the transaction; Throw an exception if there is no current transaction
REQUIRES_NEWCreate a new transaction. If there is a current transaction, suspend the current transaction
NOT_SUPPORTEDRun in non transactional mode. If there is a current transaction, suspend the current transaction
NEVERRun in non transactional mode, and throw an exception if a transaction currently exists
NESTEDIf a transaction currently exists, create a transaction to run as a nested transaction of the current transaction; If there is no transaction at present, this value is equivalent to transactiondefinition.promotion_ REQUIRED

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

Configuration in TransactionTemplate

transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

Configuration in platform transaction manager

public void update2() {
    //Create default configuration for transactions
    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 transaction
        platformTransactionManager.commit(status);
    } catch (DataAccessException e) {
          e.printStackTrace();
        //RollBACK 
        platformTransactionManager.rollback(status);
    }
}

Configuration of declarative transactions (XML)

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!--with add Start method, add transaction-->
        <tx:method name="add*"/>
        <tx:method name="insert*" isolation="SERIALIZABLE" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

Configuration of declarative transactions (Java)

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

That's how to use it. As for the specific meaning of the seven kinds of communication, I'll tell you one by one right away.

5.2.1,REQUIRED

REQUIRED indicates that if a transaction currently exists, the transaction will be joined; If there is no current transaction, a new transaction is created.

For example, I have the following code:

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

I call handle1 in the handle2 method.

Then:

  1. If the handle2 method itself has a transaction, the handle1 method will be added to the transaction where the handle2 method is located. In this way, the two methods will be in the same transaction and succeed or fail together (no matter who throws an exception in handle2 or handle1, the overall rollback will be caused).

  2. If the handle2 method itself has no transaction, the handle1 method will open a new transaction and play by itself.

Take a simple example: handle2 method has transactions, and handle1 method also has transactions (partners configure transactions themselves according to the previous explanation). The transaction log printed by the project is 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 transaction

It can be seen from the log that one transaction has been started before and after. There is a sentence in the log:

Participating in existing transaction

This shows that the handle1 method does not start the transaction itself, but joins the transaction of the handle2 method.

5.2.2,REQUIRES_NEW

REQUIRES_NEW means to create a new transaction. If there is a current transaction, suspend the current transaction. In other words, requirements does not matter whether the external method has transactions or not_ New will start its own business.

Brother song needs to say more. Some little friends may think requirements_ New and REQUIRED are so similar that there seems to be no difference. In fact, if you simply look at the final rollback effect, you may not see any difference. However, please pay attention to the bold above SongGe in requirements_ There may be two transactions in new at the same time. The transaction of the external method is suspended and the transaction of the internal method runs alone. This will not happen in REQUIRED. If the propagation of both internal and external methods is REQUIRED, it is only a transaction in the end.

As in the above example, suppose that both handle1 and handle2 methods have transactions, the transaction propagation of handle2 method is REQUIRED, and the transaction propagation of handle1 method is requirements_ New, the final printed transaction log is 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@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 transaction

By analyzing this log, we can see:

  1. First, a transaction is opened for the handle2 method.

  2. After executing the SQL of the handle2 method, the transaction is suspended.

  3. A new transaction is opened for the handle1 method.

  4. Execute the SQL of the handle1 method.

  5. Commit the transaction for the handle1 method.

  6. Resume the suspended transaction (Resuming).

  7. Commit the transaction for the handle2 method.

From this log, you can clearly see requirements_ The difference between new and REQUIRED.

SongGe would like to make a brief summary (assuming that the transaction propagation of handle1 method is requirements_new):

  1. If the handle2 method has no transaction, the handle1 method starts a transaction and plays by itself.

  2. If the handle2 method has a transaction, the handle1 method will still open a transaction. At this time, if an exception occurs in handle2, rollback will not result in the rollback of handle1 method, because handle1 method is an independent transaction; If an exception occurs in the handle1 method, which causes rollback, and the exception of the handle1 method is not captured and transferred to the handle2 method, it will also cause the handle2 method to rollback.

Partners in this place should pay a little attention. When we test, because there are two updated SQL, if the updated query field is not an index field, InnoDB will use the table lock, which will lead to deadlock (the table lock is opened during the execution of handle2 method, resulting in the waiting of handle1 method, and handle2 can release the lock only after the execution of handle1 method). Therefore, in the above test, we will set the username field as the index field, so that the row lock is used by default.

5.2.3,NESTED

NESTED means that if a transaction currently exists, a transaction will be created to run as a NESTED transaction of the current transaction; If there is no transaction currently, this value is equivalent to

TransactionDefinition.PROPAGATION_REQUIRED

Assuming that handle2 method has transactions and handle1 method also has transactions and the propagation is NESTED, the transaction log finally executed 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@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 transaction

The key sentence is   Creating nested transaction.

At this time, the internal method (handle1) modified by NESTED belongs to the sub transaction of the external transaction. If the external main transaction is rolled back, the sub transaction will also be rolled back, and the internal sub transaction can be rolled back separately without affecting the external main transaction and other sub transactions (exceptions of the internal sub transaction need to be handled).

5.2.4,MANDATORY

MANDATORY means that if there is a transaction, the transaction will be joined; Throw an exception if there is no current transaction.

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

Assuming that handle2 method has transactions and handle1 method also has transactions, and the propagation is MANDATORY, the transaction log finally executed 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@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 transaction

As can be seen from this log:

  1. First, open the transaction for the handle2 method.

  2. Execute the SQL of the handle2 method.

  3. The handle1 method is added to an existing transaction.

  4. Execute the SQL of the handle1 method.

  5. Commit the transaction.

Assuming that handle2 method has no transactions and handle1 method has transactions and the propagation is MANDATORY, the following exceptions will be thrown during final execution:

No existing transaction found for transaction marked with propagation 'mandatory'

An error occurred because there is no existing transaction.

5.2.5,SUPPORTS

SUPPORTS means that if there is a transaction currently, the transaction will be joined; If there are currently no transactions, continue to run in a non transactional manner.

This is also simple. You will understand it by giving two examples.

Assuming that handle2 method has transactions and handle1 method also has transactions and the propagation is SUPPORTS, 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 transaction

This log is very simple. There's nothing to say. Recognize it   Participating in existing transaction   Indicates that you can join an existing transaction.

Assuming that handle2 method has no transactions and handle1 method has transactions and the propagation is SUPPORTS, the transaction will not be started in the end and there is no relevant log.

5.2.6,NOT_SUPPORTED

NOT_SUPPORTED means to run in a non transaction mode. If there is a current transaction, the current transaction will be suspended.

Suppose handle2 method has transactions, handle1 method also has transactions, and the propagation is NOT_SUPPORTED, 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 transaction

Just recognize these two sentences in this log:

  • Suspending current transaction   Indicates that the current transaction is suspended;
  • Resuming suspended transaction   Indicates to resume a pending transaction.

5.2.7,NEVER

NEVER means to run in a non transaction mode. If there is a transaction, an exception will be thrown.

Assuming that handle2 method has transactions and handle1 method also has transactions and the propagation is NEVER, the following exception will be thrown in the end:

Existing transaction found for transaction marked with propagation 'never'

5.3 rollback rules

By default, transactions will be rolled back only when they encounter runtime exceptions (subclasses of RuntimeException) and errors, and will not be rolled back when they encounter checked exceptions.

Like 1 / 0 and null pointers, these are runtimeexceptions, while IOException is a Checked Exception. In other words, by default, IOException will not cause transaction rollback.

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

Java configuration:

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

In addition, we can also specify not to roll back when some exceptions occur. For example, when the system throws an ArithmeticException exception and does not trigger transaction rollback, the configuration method is as follows:

Java configuration:

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

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>

5.4 read only

Read only transactions are generally set on query methods, but not all query methods need read-only transactions, depending on the specific situation.

Generally speaking, if this business method has only one query SQL, there is no need to add transactions. Forcibly adding is 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 start an independent transaction. In this way, if data is modified by concurrent operations, different data will be found in multiple query SQL. At this time, if we start the transaction and set it as a read-only transaction, multiple query SQL will be placed in the same transaction, and multiple identical SQL executed in the transaction will obtain the same query results.

Read only transactions are set as follows:

Java configuration:

@Transactional(readOnly = true)

XML configuration:

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

5.5 timeout

Timeout refers to the maximum time allowed for a transaction to execute. If the time limit is exceeded but the transaction has not been completed, the transaction will be rolled back automatically.

The transaction timeout is configured as follows (in seconds):

Java configuration:

@Transactional(timeout = 10)

XML configuration:

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

stay   TransactionDefinition   The timeout time is represented by the value of int in. Its unit is seconds. The default value is - 1.

6. Precautions

  1. Transactions can only be valid if they are applied to public methods.

  2. The transaction needs to be called from outside, and the Spring self calling transaction will become invalid. That is, in the same category, method A has no transactions, method B has transactions, and method A calls method B, then the transactions of method B will fail. This should be noted in particular, because the proxy mode only intercepts the external method calls passed in through the proxy, so the self calling transaction does not take effect.

  3. It is recommended that the transaction annotation @ Transactional be added to the implementation class rather than defined on the interface. If it is added to the interface class or interface method, the annotation will take effect only when the interface based proxy is configured.

Topics: Spring Transaction