Interview Blitz 83: What would cause a @Transactional transaction to fail?

It is impossible to have no transactions in a program. In Spring, transactions are implemented in two ways: programmatic transactions and declarative transactions. Because programmatic transactions are relatively cumbersome to implement and declarative transactions are extremely simple to implement, we use declarative transactions@Transactional to implement transactions in our daily projects.

@Transactional is extremely simple to use. You only need to add the @Transactional keyword to a class or method to automatically open, commit or roll back a transaction. Its basic usage is as follows:

@Transactional 
@RequestMapping("/add")
public int add(UserInfo userInfo) {
    int result = userService.add(userInfo);
    return result;
}

@Transactional execution process

@Transactional will automatically open the transaction before the method is executed; after the method is successfully executed, the transaction will be automatically committed; if an exception occurs during the execution of the method, it will automatically roll back the transaction.

However, the seemingly simple @Transactional hides some "pits". These pits are the topic we are going to talk about today: What are the common scenarios that cause @Transactional transactions to fail?

Before we start, we need to clarify a definition, what is "failure"?

"Failure" in this article refers to "losing (its) efficacy", that is, when @Transactional does not meet our expected results, we can say that @Transactional is invalid.

What are the scenarios where @Transactional fails? Let's take a look at them one by one.

1. Non-public modified methods

When the method modified by @Transactional is non-public, the transaction is invalid. For example, the following code cannot automatically rollback when an exception is encountered:

@RequestMapping("/save")
int save(UserInfo userInfo) {
    // non-null test
    if (userInfo == null ||
        !StringUtils.hasLength(userInfo.getUsername()) ||
        !StringUtils.hasLength(userInfo.getPassword()))
        return 0;
    // perform an add operation
    int result = userService.save(userInfo);
    System.out.println("add Affected rows:" + result);
    int num = 10 / 0; // set an exception here
    return result;
}

The result of running the above program is as follows:

When a runtime exception occurs in the program, our expected result is that the transaction should be automatically rolled back, that is, adding users fails. However, when we query the database, we find that the transaction does not perform the rollback operation. The data in the database is shown in the following figure. :

2.timeout timeout

When a smaller timeout is set on @Transactional, if the execution time of the method itself exceeds the set timeout timeout, the method that should normally insert data will fail to execute. The sample code is as follows:

@Transactional(timeout = 3) // The timeout is 3s
@RequestMapping("/save")
int save(UserInfo userInfo) throws InterruptedException {
    // non-null test
    if (userInfo == null ||
        !StringUtils.hasLength(userInfo.getUsername()) ||
        !StringUtils.hasLength(userInfo.getPassword()))
        return 0;
    int result = userService.save(userInfo);
    return result;
}

The save method of UserService is implemented as follows:

public int save(UserInfo userInfo) throws InterruptedException {
    // sleep 5s
    TimeUnit.SECONDS.sleep(5);
    int result = userMapper.add(userInfo);
    return result;
}

The result of running the above program is as follows:

The database does not insert data correctly, as shown in the following image:

3. There are try/catch in the code

In the previous execution flow of @Transactional, we mentioned that when an exception occurs in the method, the transaction will be automatically rolled back. However, if try/catch is added to the program, @Transactional will not automatically roll back the transaction. The sample code is as follows:

@Transactional
@RequestMapping("/save")
public int save(UserInfo userInfo) throws InterruptedException {
    // non-null test
    if (userInfo == null ||
        !StringUtils.hasLength(userInfo.getUsername()) ||
        !StringUtils.hasLength(userInfo.getPassword()))
        return 0;
    int result = userService.save(userInfo);
    try {
        int num = 10 / 0; // set an exception here
    } catch (Exception e) {
    }
    return result;
}

The result of running the above program is as follows:

At this point, when querying the database, we found that the program did not perform the rollback operation, and a piece of data was successfully added to the database, as shown in the following figure:

4. Call the @Transactional method inside the class

When calling a method decorated with @Transactional inside a class, the transaction will not take effect. The sample code is as follows:

@RequestMapping("/save")
public int saveMappping(UserInfo userInfo) {
    return save(userInfo);
}
@Transactional
public int save(UserInfo userInfo) {
    // non-null test
    if (userInfo == null ||
        !StringUtils.hasLength(userInfo.getUsername()) ||
        !StringUtils.hasLength(userInfo.getPassword()))
        return 0;
    int result = userService.save(userInfo);
    int num = 10 / 0; // set an exception here
    return result;
}

In the above code, we added the @Transactional declarative transaction in the add method save, and added the exception code. Our expected result is that the program is abnormal and the transaction is automatically rolled back. The execution result of the above program is as follows:

However, when we query the database, we find that the program execution does not meet our expectations, and the added data is not automatically rolled back, as shown in the following figure:

5. The database does not support transactions

The @Transactional in our program only sends instructions to the invoked database to start a transaction, commit a transaction, and roll back a transaction, but if the database itself does not support transactions, such as if the MyISAM engine is set up in MySQL, then it does not support transactions. In this case, even if the @Transactional annotation is added to the program, there will be no transaction behavior. It's hard for a clever woman to cook without rice.

Summarize

When declarative transaction @Transactional encounters the following scenarios, the transaction will fail:

  1. non-public modified methods;
  2. The timeout setting is too small;
  3. Use try/catch in code to handle exceptions;
  4. Call the @Transactional method inside the class;
  5. The database does not support transactions.

References & Acknowledgements

www.cnblogs.com/frankyou/p/12691463.html

It is up to oneself to judge right and wrong, to listen to others, and to count the gains and losses.

Official Account: Analysis of Java Interview Questions

Interview collection: https://gitee.com/mydb/interview

Tags: Java

Posted by Dragoon1 on Wed, 14 Sep 2022 21:10:37 +0300