Explain spring declarative transactions (@ Transactional) in detail

spring transactions are used in two ways: programmatic transactions and declarative transactions.

Programming transaction Last It has been introduced in the article. For unfamiliar suggestions, first look at the usage of programmatic transactions.

This article mainly introduces the usage of declarative transactions. We basically use declarative transactions in our work, so this article is more important. I suggest you cheer up and start formally.

What is a declarative transaction?

The so-called declarative transaction is to tell spring which methods need spring to help manage transactions through configuration, such as configuration files (xml) or annotations. Then developers only focus on business code, and spring automatically controls the transactions.

For example, the annotation method only needs to add an @ Transaction annotation on the method, so spring will automatically start a Transaction before the method is executed. After the method is executed, it will automatically commit or roll back the Transaction, and there is no Transaction related code in the method, which is a special method.

@Transaction
public void insert(String userName){
    this.jdbcTemplate.update("insert into t_user (name) values (?)", userName);
}

Two implementations of declarative transactions

  1. The way of configuration file, that is, unified configuration in spring xml file, developers basically don't need to pay attention to transactions. They don't need to care about any transaction related code in the code, and everything is handed over to spring for processing.

  2. The annotation method only needs to add the @ Transaction annotation to the method that needs spring to help manage transactions. The annotation method is relatively simpler and needs to be configured by the developer. Some students may not be familiar with spring, so there are certain risks in configuring this. Just do a good code review.

The method of configuration file is not discussed here. It is relatively rarely used. We mainly master the method of annotation and how to use it.

The declarative transaction annotation method consists of five steps

1. Enable annotation driven transaction management in Spring

Add the @ EnableTransactionManagement annotation to the spring configuration class

@EnableTransactionManagement
public class MainConfig4 {
}

Briefly introduce the principle: when the spring container is started, the @ EnableTransactionManagement annotation is found. At this time, the creation of all beans will be blocked. Scan to see if there is @ Transaction annotation on the bean (this annotation can be found in the class, parent class, interface or method). If there is this annotation, Spring will generate proxy objects for beans through aop. An interceptor will be added to the proxy object. The interceptor will intercept the execution of the public method in the bean, start the Transaction before the method is executed, and commit or roll back the Transaction after the method is executed. There will be a special article later to show you the source code of this piece.

If you are interested, you can read the source code first, mainly the following method

org.springframework.transaction.interceptor.TransactionInterceptor#invoke

Let's take a look at the source code of enable transaction management

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {

 /**
  * spring Transaction management is realized by creating proxy objects for bean s through aop
  * There are two ways to create proxy objects, jdk dynamic proxy and cglib proxy
  * proxyTargetClass: When it is true, cglib is forced to be used to create the proxy
  */
 boolean proxyTargetClass() default false;

 /**
  * Used to specify the order of transaction interceptors
  * We know that many interceptors can be added to a method, and the interceptors can specify the order
  * For example, you can customize some interceptors and execute them before or after the transaction interceptor, which can be controlled through order
  */
 int order() default Ordered.LOWEST_PRECEDENCE;
}

2. Define transaction manager

If the transaction is managed by spring, you must create one or more transaction managers to manage specific transactions, such as starting a transaction, committing a transaction, and rolling back a transaction.

spring uses the platform transaction manager interface to represent the transaction manager.

PlatformTransactionManager has multiple implementation classes to cope with different environments

jpa transaction manager: if you use jpa to operate db, you need this manager to help you control transactions.

Data source transaction manager: if you specify the data source, such as JdbcTemplate, mybatis and ibatis, you need to use this manager to help you control transactions.

Hibernate transaction manager: if you use hibernate to operate db, you need this manager to help you control transactions.

jta transaction manager: if you use jta in java to operate db, this is usually a distributed transaction. At this time, you need to use this manager to control transactions.

For example, if we use mybatis or JDBC template, define a transaction manager in the following way.

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

3. The @ Transaction annotation needs to be added to the target of the Transaction

  • @If the Transaction is placed on the interface, all public in the implementation class of the interface will be automatically added to the Transaction by spring

  • @If the Transaction is placed on the class, all pubilc methods in the current class and its infinite subclasses will be automatically added to the Transaction by spring

  • @If the Transaction is placed on the public method, the method will be automatically added to the Transaction by spring

  • Note: @ Transaction is only valid for public methods

Let's take a look at the @ Transactional source code:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

    /**
     * Specify the bean name of the transaction manager. If there are multiple transaction managers PlatformTransactionManager in the container,
     * Then you have to tell spring which transaction manager needs to be used in the current configuration
     */
    @AliasFor("transactionManager")
    String value() default "";

    /**
     * Select one of value, value and transactionManager, or it can be empty. If it is empty, a transaction manager bean will be found from the container by type by default
     */
    @AliasFor("value")
    String transactionManager() default "";

    /**
     * Propagation properties of transactions
     */
    Propagation propagation() default Propagation.REQUIRED;

    /**
     * The isolation level of transactions is to set the isolation level of the database. Do you know the isolation level of the database? If you don't know, you can make it up
     */
    Isolation isolation() default Isolation.DEFAULT;

    /**
     * Timeout of transaction execution (seconds). If there is a problem, I can't wait for you for a day. Maybe I can only wait for you for 10 seconds at most
     * 10 Seconds later, a timeout exception will pop up before the execution is completed
     */
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

    /**
     * Whether it is a read-only transaction. For example, if there are only query operations in a method, we can specify that the transaction is read-only
     * If this parameter is set, the database may perform some performance optimization to improve the query speed
     */
    boolean readOnly() default false;

    /**
     * Define zero (0) or more exception classes. These exception classes must be subclasses of Throwable. When the method throws these exceptions and their subclasses, spring will roll back the transaction
     * If it doesn't deserve to be done, it will default to   RuntimeException   perhaps   Error   The transaction is rolled back only if  
     */
    Class<? extends Throwable>[] rollbackFor() default {};

    /**
     * and   rollbackFor   The function is the same, but the class name is used in this place
     */
    String[] rollbackForClassName() default {};

    /**
     * Define zero (0) or more exception classes. These exception classes must be subclasses of Throwable. When the method throws these exceptions, the transaction will not be rolled back
     */
    Class<? extends Throwable>[] noRollbackFor() default {};

    /**
     * and   noRollbackFor   The function is the same, but the class name is used in this place
     */
    String[] noRollbackForClassName() default {};

}

Parameter introduction

parameterdescribe
valueSpecify the bean name of the transaction manager. If there are multiple transaction managers platform transaction manager in the container, you have to tell spring which transaction manager needs to be used in the current configuration
transactionManagerSelect one of value, value and transactionManager, or it can be empty. If it is empty, a transaction manager bean will be found from the container by type by default
propagationThe propagation properties of transactions are described in detail in the next article
isolationThe isolation level of transactions is to set the isolation level of the database. Do you know the isolation level of the database? If you don't know, you can make it up
timeoutTimeout of transaction execution (seconds). If there is a problem, I can't wait for you for a day. Maybe I can only wait for you for 10 seconds at most. Before the execution is completed, a timeout exception will pop up
readOnlyWhether it is a read-only transaction. For example, there are only query operations in a method. We can specify that the transaction is read-only. If this parameter is set, the database may do some performance optimization to improve the query speed
rollbackForDefine zero (0) or more exception classes. These exception classes must be subclasses of Throwable. When the method throws these exceptions and their subclasses, spring will roll back the transaction. If not, the transaction will be rolled back by default under the condition of RuntimeException or Error
rollbackForClassNameThe same as rollbackFor, except that the class name is used in this place
noRollbackForDefine zero (0) or more exception classes. These exception classes must be subclasses of Throwable. When the method throws these exceptions, the transaction will not be rolled back
noRollbackForClassNameThe same as noRollbackFor, except that the class name is used in this place

4. Execute db Business Operations

When performing business operations on the @ Transaction annotation class or target methods, these methods will be automatically managed by spring.

For example, the following insertBatch operation deletes data first, and then inserts data in batches. The @ Transactional annotation is added to the method. At this time, this method will be automatically controlled by spring transactions, either successful or failed.

@Component
public class UserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    //Clear the data in the table first, and then insert the data in batches. Either both succeed or fail
    @Transactional
    public void insertBatch(String... names) {
        jdbcTemplate.update("truncate table t_user");
        for (String name : names) {
            jdbcTemplate.update("INSERT INTO t_user(name) VALUES (?)", name);
        }
    }
}

5. Start the spring container and use the bean to perform business operations

@Test
public void test1() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig4.class);
    context.refresh();

    UserService userService = context.getBean(UserService.class);
    userService.insertBatch("java High concurrency series", "mysql series", "maven series", "mybatis series");
}

Case 1

Prepare database

DROP DATABASE IF EXISTS javacode2018;
CREATE DATABASE if NOT EXISTS javacode2018;

USE javacode2018;
DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user(
  id int PRIMARY KEY AUTO_INCREMENT,
  name varchar(256) NOT NULL DEFAULT '' COMMENT 'full name'
);

spring configuration class

package com.javacode2018.tx.demo4;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;

@EnableTransactionManagement //@1
@Configuration
@ComponentScan
public class MainConfig4 {
    //Define a data source
    @Bean
    public DataSource dataSource() {
        org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("root123");
        dataSource.setInitialSize(5);
        return dataSource;
    }

    //Define a JdbcTemplate to perform db operations
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    //I define a transaction manager
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) { //@2
        return new DataSourceTransactionManager(dataSource);
    }
}

@1: Use the @ EnableTransactionManagement annotation to enable spring transaction management

@2: Define transaction manager

A business class

package com.javacode2018.tx.demo4;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;

@Component
public class UserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    //Clear the data in the table first, and then insert the data in batches. Either both succeed or fail
    @Transactional //@1
    public int insertBatch(String... names) {
        int result = 0;
        jdbcTemplate.update("truncate table t_user");
        for (String name : names) {
            result += jdbcTemplate.update("INSERT INTO t_user(name) VALUES (?)", name);
        }
        return result;
    }

    //Get all user information
    public List<Map<String, Object>> userList() {
        return jdbcTemplate.queryForList("SELECT * FROM t_user");
    }
}

@1: The @ Transactional annotation is added to the insertBatch method, allowing spring to automatically add transactions to this method

Test class

package com.javacode2018.tx.demo4;

import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo4Test {
    @Test
    public void test1() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig4.class);
        context.refresh();

        UserService userService = context.getBean(UserService.class);
        //Insert first
        int count = userService.insertBatch(
                "java High concurrency series",
                "mysql series",
                "maven series",
                "mybatis series");
        System.out.println("Successfully inserted (items):" + count);
        //Then check it
        System.out.println(userService.userList());
    }
}

Run output

Insert succeeded (item): 4
[{id=1, name=java High concurrency series}, {id=2, name=mysql series}, {id=3, name=maven series}, {id=4, name=mybatis series}]

Some friends may ask, how do you know if the called method uses transactions? Let's take a look.

How to determine whether spring transactions are used in the method

Mode 1: breakpoint debugging

spring transactions are handled by the TransactionInterceptor interceptor. Finally, the following method will be called. Set a breakpoint to see the detailed process.

org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

Method 2: read the log

The process of spring transaction processing has detailed log output. Turn on the log, and the console can see the detailed process of the transaction.

Add maven configuration

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

src\main\resources create a new logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%d{MM-dd HH:mm:ss.SSS}][%thread{20}:${PID:- }][%X{trace_id}][%level][%logger{56}:%line:%method\(\)]:%msg%n##########**********##########%n</pattern>
        </encoder>
    </appender>

    <logger name="org.springframework" level="debug">
        <appender-ref ref="STDOUT" />
    </logger>

</configuration>

Run case 1 again

[09-10 11:20:38.830][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:370:getTransaction()]:Creating new transaction with name [com.javacode2018.tx.demo4.UserService.insertBatch]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
##########**********##########
[09-10 11:20:39.120][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:265:doBegin()]:Acquired Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@65fe9e33]]] for JDBC transaction
##########**********##########
[09-10 11:20:39.125][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:283:doBegin()]:Switching JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@65fe9e33]]] to manual commit
##########**********##########
[09-10 11:20:39.139][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:502:update()]:Executing SQL update [truncate table t_user]
##########**********##########
[09-10 11:20:39.169][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:860:update()]:Executing prepared SQL update
##########**********##########
[09-10 11:20:39.169][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:609:execute()]:Executing prepared SQL statement [INSERT INTO t_user(name) VALUES (?)]
##########**********##########
[09-10 11:20:39.234][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:860:update()]:Executing prepared SQL update
##########**********##########
[09-10 11:20:39.235][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:609:execute()]:Executing prepared SQL statement [INSERT INTO t_user(name) VALUES (?)]
##########**********##########
[09-10 11:20:39.236][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:860:update()]:Executing prepared SQL update
##########**********##########
[09-10 11:20:39.237][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:609:execute()]:Executing prepared SQL statement [INSERT INTO t_user(name) VALUES (?)]
##########**********##########
[09-10 11:20:39.238][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:860:update()]:Executing prepared SQL update
##########**********##########
[09-10 11:20:39.239][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:609:execute()]:Executing prepared SQL statement [INSERT INTO t_user(name) VALUES (?)]
##########**********##########
[09-10 11:20:39.241][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:741:processCommit()]:Initiating transaction commit
##########**********##########
[09-10 11:20:39.241][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:328:doCommit()]:Committing JDBC transaction on Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@65fe9e33]]]
##########**********##########
[09-10 11:20:39.244][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:387:doCleanupAfterCompletion()]:Releasing JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@65fe9e33]]] after transaction
##########**********##########
Insert succeeded (item): 4
[09-10 11:20:39.246][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:427:query()]:Executing SQL query [SELECT * FROM t_user]
##########**********##########
[09-10 11:20:39.247][main: ][][DEBUG][org.springframework.jdbc.datasource.DataSourceUtils:115:doGetConnection()]:Fetching JDBC Connection from DataSource
##########**********##########
[{id=1, name=java High concurrency series}, {id=2, name=mysql series}, {id=3, name=maven series}, {id=4, name=mybatis series}]

Let's understand the log

The insertBatch method has @ Transaction annotation, so it will be intercepted by the interceptor. The following is a Transaction created before the insertBatch method is called.

The @ Transaction annotation parameters on the insertBatch method are all default values. In the @ Transaction annotation, the Transaction manager can be specified through value or transactionManager, but it is not specified. At this time, spring will find a default in the container according to the Transaction manager type. We just defined one in the spring container, so we used it directly. In the Transaction manager, we use the new DataSourceTransactionManager(dataSource) to obtain a database connection from the datasource of the Transaction manager, then set the Transaction to manual submission through the connection, and then throw (datasource - > this connection) into ThreadLocal. See why Last article.

Now just go inside the insertBatch method and execute some db operations through the JDBC template. The JDBC template will get the spring transaction connection in the threadlocal above through the datasource, and then execute the db operation.

Finally, after the insertBatch method is executed without any exception, spring starts to commit transactions through the database connection.

summary

This article explains the steps of using programmatic transactions in spring.

Two notes are mainly involved:

@Enable transaction management: enables the spring transaction management function

@Transaction: adding it to the classes, methods and interfaces that need spring to manage transactions will only be effective for public methods.

If you have any questions, please leave a message.

The next article will introduce the propagation properties of transactions in detail. Please look forward to it.

Case source code

git Address:
https://gitee.com/javacode2018/spring-series

Corresponding source code of this case: spring-series\lesson-002-tx\src\main\java\com\javacode2018\tx\demo4

All the case codes of passerby a java will be put on this in the future. Let's watch it and continue to pay attention to the dynamics.

Keywords: Spring

Added by Manat on Thu, 23 Sep 2021 18:36:52 +0300