Spring transactions and transaction propagation mechanism

1. Business review

1. What is a transaction

Transaction definition: Encapsulate a set of operations into an execution unit (encapsulate them together), either all succeed or all fail.

2. Why use transactions?

Let’s use a cliché example.

Zhang San transfers money to Li Si: 

Zhang San’s account-100;

John Doe’s account +100; 

If an exception occurs when Zhang San's account is -100, and the operation of Li Si's account +100 has not been completed at this time, the money in Zhang San's account will disappear for no reason. If you use transactions, you can solve this problem and let this happen. A group of operations either succeeds together or fails together.

3. Transaction usage in MySQL 

--Open transaction
start transaction;
--Business execution
--Submit transaction
commit;
--rollback transaction
rollback;

2. Transactions in Spring

0.Preparation

1. First create the database

        -- 创建数据库
        drop database if exists mycnblog;
        create database mycnblog default character set utf8mb4;
        -- 使⽤数据数据
        use mycnblog;
        -- 创建表[⽤户表]
        drop table if exists userinfo;
        create table userinfo(
        id int primary key auto_increment,
        username varchar(100) not null,
        password varchar(32) not null,
        photo varchar(500) default '',
        createtime datetime default now(),
        updatetime datetime default now(),
        `state` int default 1
        ) default charset 'utf8mb4';

        -- 添加⼀个⽤户信息
        insert into `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`,
        `createtime`, `updatetime`, `state`) values
        (1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1)
        ;
        -- 添加⼀个用户日志表
        DROP TABLE IF EXISTS userlog;
        CREATE TABLE userlog(
        id INT PRIMARY KEY AUTO_INCREMENT,
        username VARCHAR(100) NOT NULL,
        createtime DATETIME DEFAULT NOW( ),
        updatetime DATETIME DEFAULT NOW()
        ) DEFAULT CHARSET 'utf8mb4';

2. Entity class

@Data
public class User {
    private Integer id;
    private String username;
    private String password;
    private String photo;
    private Date createtime;
    private Date updatetime;
}
@Data
public class UserLog {
    private Integer id;
    private String username;
    private Date cratetime;
    private Date updatetime;

    public UserLog() {
    }

    public UserLog(String username) {
        this.username = username;
    }
}

3.Mapper

@Mapper
public interface UserMapper {
    @Select("select * from userinfo")
    List<User> selectAll();
     @Insert("insert into userinfo(username,password) values (#{username},#{password}))")
    Integer insert(User user);
}
@Mapper
public interface UserLogMapper {
    @Insert("insert into userlog(username) values (#{username})")
    Integer insert(UserLog userLog);
}

4.Service

@Service
public class UserService {
    @Autowired
    UserMapper userMapper;

    public List<User> selectAll() {
        return userMapper.selectAll();
    }

    public Integer addUser(User user) {
        return userMapper.insert(user);
    }
}
@Service
public class UserLogService {
    @Autowired
    UserLogMapper userLogMapper;

    public Integer addUser(UserLog userLog) {
        return userLogMapper.insert(userLog);
    }
}
Transaction operations in Spring are divided into two categories:
1. Programmatic transactions (manually write code to operate transactions).
2. Declarative transactions (using annotations to automatically open and submit transactions).
Ordinary add operation
@RestController
@Slf4j
@RequestMapping("/trans")
public class TransactionalController {
    @Autowired
    UserService userService;

    @RequestMapping("/add")
    public Integer addUser() {
        User user = new User();
        user.setName("zxn123");
        user.setPwd("123456");
        return userService.addUser(user);

    }
}

 You can see that it was added successfully

1. Manual

 Rollback after joining the transaction

@RestController
@Slf4j
@RequestMapping("/trans")
public class TransactionalController {
    @Autowired
    private UserService userService;
    //数据库事务管理器
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    //事务属性
    @Autowired
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/add")
    public Integer addUser(String username, String password) {
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        User user = new User();
        user.setName(username);
        user.setPwd(password);
        Integer insert = userService.addUser(user);
        log.info("影响了" + insert + "行");
        dataSourceTransactionManager.rollback(transaction);
        return insert;

    }
}

Still perform the above operation. The database has not changed, indicating that the data has been rolled back.

 Perform rollback log after joining the transaction

  Commit log after joining the transaction

 2. Annotation

Add @Transactional annotation

@RestController
@Slf4j
@RequestMapping("/trans2")
public class TransactionalController2 {
    @Autowired
    private UserService userService;
    @Transactional
    @RequestMapping("/add")
    public Integer addUser(String username, String password) {
        User user = new User();
        user.setName(username);
        user.setPwd(password);
        Integer insert = userService.addUser(user);
        log.info("影响了" + insert + "行");
        return insert;

    }
}

1. No exception, automatic submission

2. If there is an exception, automatically rollback

 You can see that there is no submission operation.

3. @Transactional Scope

@Transactional can be used to modify methods or classes:
When modifying methods: Please note that it can only be applied to public methods, otherwise it will not take effect. This usage is recommended.
When modifying a class: Indicates that the annotation is effective for all public methods in the class.

4. @Transactional parameter description

parameter effect
value When multiple transaction managers are configured, this property can be used to specify which transaction manager is selected.
transactionManager When multiple transaction managers are configured, this property can be used to specify which transaction manager is selected.
propagation Transaction propagation behavior, default value Propagation.REQUIRED
isolation The isolation level of the transaction, the default value is lsolation.DEFAULT
timeout The timeout period of the transaction, the default value is -1. If the time limit is exceeded but the transaction has not been completed, the transaction will be automatically rolled back.
readOnly
 
Specifies whether the transaction is a read-only transaction. The default value is false; in order to ignore methods that do not require transactions, such as reading data, you can set read-only to true.
rollbackFor Used to specify the exception type that can trigger transaction rollback. Multiple exception types can be specified.
rollbackForClassName Used to specify the exception type that can trigger transaction rollback. Multiple exception types can be specified.
noRollbackFor Throws the specified exception type without rolling back the transaction. You can also specify multiple exception types.
noRollbackForClassName
 
Throws the specified exception type without rolling back the transaction. You can also specify multiple exception types.

Demo noRollbackFor

    @Transactional(noRollbackFor = ArithmeticException.class)
    @RequestMapping("/add")
    public Integer addUser(String username, String password) {
        User user = new User();
        user.setName(username);
        user.setPwd(password);
        Integer insert = userService.addUser(user);
        log.info("影响了" + insert + "行");
        int a = 10 / 0;
        return insert;

    }

At this point you can see that there is an ArithmeticException error, we add data

 You can see that it was added successfully 

@Transactional defaults to rollback only when encountering runtime exceptions and Errors. Non-runtime exceptions do not rollback. That is,
in the subclasses of Exception, except for RuntimeException and its subclasses

 Therefore, we can set it ourselves to roll back all exceptions.

 @Transactional(rollbackFor = Exception.class)

5. Notes on annotations

@Transactional When the exception is caught, the transaction will not be automatically rolled back.

    @RequestMapping("/add2")
    @Transactional
    public Integer add2(String username, String password) {
        User user = new User();
        user.setName(username);
        user.setPwd(password);
        // 插⼊数据库
        int result = userService.addUser(user);
        try {
            // 执⾏了异常代码(0不能做除数)
            int i = 10 / 0;
        } catch (Exception e) {
            log.info(e.getMessage());
        }
        return result;
    }

 At this point you can see that the addition is still successful.

3. Transaction isolation level

1. Characteristics of transactions

  1. Atomicity Atomicity means that a transaction is an indivisible unit of work , and operations in a transaction either all occur or none occur.

  2. Consistency transactions must transform the database from one consistency state to another .

  3. Isolation: The isolation of a transaction means that the execution of a transaction cannot be interfered by other transactions. That is, the operations and data used within a transaction are isolated from other concurrent transactions, and concurrently executed transactions cannot interfere with each other. . (See the isolation level of the transaction for details)

  4. Durability (Durability) Durability means that once a transaction is committed , its changes to the data in the database are permanent, and subsequent other operations and database failures should not have any impact on it.

2. Transaction isolation level

  • Dirty read : For two transactions T1, T2, T1 reads fields that have been updated by T2 but have not yet been committed . Later, if T2 rolls back, the content read by T1 will be temporary and invalid.

  • Non-repeatable read : For two transactions T1, T2, T1 reads a field, and then T2 updates the field. Afterwards, T1 reads the same field again and the value is different.

  • Phantom read : For two transactions T1, T2, T1 reads a field from a table, and then T2 inserts some new rows in the table. Later, if T1 reads the same table again, there will be a few more rows.

transaction isolation level dirty read non-repeatable read phantom reading
READ UNCOMMITTED
READ COMMITTED x
REPEATABLE READ x x
SERIALIZABLE x x x

3. Spring transaction isolation level

The transaction isolation levels in Spring include the following five types:
  1. Isolation.DEFAULT: Based on the transaction isolation level of the connected database.
  2.  Isolation.READ_UNCOMMITTED: Read uncommitted, uncommitted transactions can be read, and dirty reads exist.
  3.  Isolation.READ_COMMITTED: Reading is committed, only committed transactions can be read, dirty reads are solved, and non-repeatable reads exist.
  4. Isolation.REPEATABLE_READ: Repeatable reading solves non-repeatable reading, but there are phantom reads (MySQL default level ).
  5. Isolation.SERIALIZABLE: Serialization can solve all concurrency problems, but the performance is too low.
The transaction isolation level in Spring only needs to set the isolation attribute in @Transactional. The specific implementation code is as follows:

 @Transactional(isolation = Isolation.READ_COMMITTED)

4. Spring transaction propagation mechanism

1. What is the transaction propagation mechanism?

The Spring transaction propagation mechanism defines multiple methods that contain transactions. When calling each other, how transactions are passed between these methods.
The transaction isolation level ensures the controllability (stability) of multiple concurrent transaction executions, while the transaction propagation mechanism ensures the controllability (stability) of a transaction among multiple calling methods.
The transaction propagation mechanism solves the problem of a transaction being transmitted among multiple nodes (methods)

2. Transmission mechanism of transactions

Spring transaction propagation mechanism includes the following 7 types:
  1. Propagation.REQUIRED: The default transaction propagation level, which means that if a transaction currently exists, join the transaction; if there is currently no transaction, create a new transaction.
  2. Propagation.SUPPORTS: If there is currently a transaction, join the transaction; if there is no transaction, continue running in a non-transactional manner.
  3. Propagation.MANDATORY: (mandatory: mandatory) If there is currently a transaction, join the transaction; if there is no current transaction, throw an exception.
  4. Propagation.REQUIRES_NEW: Indicates creating a new transaction. If a transaction currently exists, suspend the current transaction. That is to say, regardless of whether the external method opens a transaction, the internal method modified by Propagation.REQUIRES_NEW will newly open its own transaction, and the opened transactions are independent of each other and do not interfere with each other.
  5. Propagation.NOT_SUPPORTED: Run in non-transactional mode. If a transaction currently exists, the current transaction will be suspended.
  6. Propagation.NEVER: Runs in non-transactional mode and throws an exception if a transaction currently exists.
  7. Propagation.NESTED: If a transaction currently exists, create a transaction to run as a nested transaction of the current transaction; if there is currently no transaction, this value is equivalent to PROPAGATION_REQUIRED

The above transaction propagation mechanisms can be divided into three categories:

 

3.Usage of Spring transaction propagation mechanism and demonstration of various scenarios 

1.Support current transaction (REQUIRED)

error-free simulation

    @Transactional(propagation = Propagation.REQUIRED)
    public Integer addUser(User user) {
        return userMapper.insert(user);
    }
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer addUserLog(UserLog userLog) {
        return userLogMapper.insert(userLog);
    }
@RestController
@Slf4j
@RequestMapping("/trans3")
public class TransactionalController3 {
    @Autowired
    private UserService userService;
    @Autowired
    private UserLogService userLogService;
    @Transactional
    @RequestMapping("/addUser")
    public boolean addUser(String username, String password) {
        User user = new User();
        user.setName(username);
        user.setPwd(password);
        //插入用户表
        userService.addUser(user);
        UserLog userLog = new UserLog(username);
        //插入日志表
        userLogService.addUserLog(userLog);
        return true;

    }
}

  

 Simulation when an error occurs

    @Transactional(propagation = Propagation.REQUIRED)
    public Integer addUserLog(UserLog userLog) {
        int i = 10 / 0;
        return userLogMapper.insert(userLog);
    }

 

 You can see that the data has not changed

2. The current transaction (REQUIRES_NEW) is not supported 

Modify isolation level

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Integer addUser(User user) {
        return userMapper.insert(user);
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Integer addUserLog(UserLog userLog) {
        int i = 10 / 0;
        return userLogMapper.insert(userLog);
    }

 You can see that userinfo indicates that the addition was successful.

3. The current transaction is not supported and NEVER throws an exception.

    @Transactional(propagation = Propagation.NEVER)
    public Integer addUser(User user) {
        return userMapper.insert(user);
    }
    @Transactional(propagation = Propagation.NEVER)
    public Integer addUserLog(UserLog userLog) {
        int i = 10 / 0;
        return userLogMapper.insert(userLog);
    }

When executed, the following error log will appear. 

4. NESTED nested transactions 

    @Transactional(propagation = Propagation.NESTED)
    public Integer addUser(User user) {
        return userMapper.insert(user);
    }
    @Transactional(propagation = Propagation.NESTED)
    public Integer addUserLog(UserLog userLog) {
        int i = 10 / 0;
        return userLogMapper.insert(userLog);
    }

 catch exception

    @Transactional(propagation = Propagation.NESTED)
    public Integer addUserLog(UserLog userLog) {
        userLogMapper.insert(userLog);
        try {
            int i = 10 / 0;
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return 1;
    }

 You can see that userinfo indicates that the addition was successful, while userlog indicates that the addition failed.

This only rolls back some transactions. If required, all transactions will be rolled back. 

The difference between nested transaction (NESTED) and joining transaction (REQUIRED):

  • If the entire transaction is executed successfully, the results of both are the same.
  • If the transaction fails in the middle of execution, the entire transaction will be rolled back after joining the transaction; while the nested transaction will be partially rolled back and will not affect the results of the execution in the previous method.
     

Guess you like

Origin blog.csdn.net/qq_64580912/article/details/131996805