Spring transaction propagation, I get asked a lot in interviews, so it's worth learning about. There are distributed transactions, which are really terrible

First, take a look at the transaction propagation levels available in Spring

1. REQUIRED (Spring’s default transaction propagation type)

  • Supports the current transaction, or creates a new transaction if there is none
  • If a transaction exists, it joins the current transaction and merges into a single transaction

2. SUPPORTS

  • Joins a transaction if it currently exists
  • If no transaction currently exists, it is run nontransactionally, which is the same as not writing

3. MANDATORY

  • Runs in the current transaction, if one currently exists
  • If there is currently no transaction, an exception is thrown, meaning that the parent method must have a transaction

4. REQUIRES_NEW

  • Create a new transaction and suspend the current transaction if one exists
  • This (REQUIRES_NEW) commits the transaction independently of the caller’s transaction, with a parent exception, and it commits normally

5. NOT_SUPPORTED

  • Run in a non-transactional manner
  • Suspends the current transaction if it exists (the caller)

6. NEVER

  • Run nontransactionally, throw an exception if a transaction is currently present, that is, the parent method must be transaction-freeBully ha ha ~

7. NESTED

  • If a transaction exists, it will become a child of the parent transaction and will not commit until the parent transaction completes
  • If there is no transaction currently, create a new transaction
  • If it is abnormal, the parent can catch its exception without rolling back and commit normally
  • But if the parent exception, it must be rolled back, which is also the sumREQUIRES_NEWREQUIRES_NEW is that if the parent is abnormal, it will still execute the transaction.

(2) The environment and code are uploaded to GithubStamp here

Create two tables DDL as follows:

CREATE TABLE `table_a` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `a_description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  `enabled` tinyint NOT NULL DEFAULT '0',
  `cuser_id` int NOT NULL DEFAULT '0',
  `uuser_id` int NOT NULL DEFAULT '0',
  `ctime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `utime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP.PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE `table_b` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `b_description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  `enabled` tinyint NOT NULL DEFAULT '0',
  `cuser_id` int NOT NULL DEFAULT '0',
  `uuser_id` int NOT NULL DEFAULT '0',
  `ctime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `utime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP.PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=35 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

Copy the code

Creating the corresponding DO Mapper service is omitted.

(3) Speak in code

1. REQUIRED (Spring’s default transaction propagation type)

  • Supports the current transaction, or creates a new transaction if there is none
  • If a transaction exists, it joins the current transaction and merges into a single transaction

It can be seen that there is no data in tables A and B, indicating that the exception in saveB method not only affects saveB method, but also causes the saveA method to roll backThe last example proves itIf a transaction exists, it joins the current transaction and merges into a single transaction

Let’s comment out the transaction annotations for the saveA method

(Note that the two methods are not in the same class. If they are in the same class, saveB will not work. See another article: juejin.cn/user/123990…)

This confirms that if there is no transaction, you create a new transaction because saveA has no transaction, so saveB creates a new transaction. (Note that this is not the saveA method.)

2. SUPPORTS

  • Joins a transaction if it currently exists
  • If no transaction currently exists, it is run nontransactionally, which is the same as not writing

Table A data:

Table B data:

It can be seen that a is successfully inserted. Although B fails to be inserted, the rollback is not performed. The data before the exception is still successfully inserted, indicating that the transaction is the same as that without transaction. If there is no transaction currently (that is, saveA, because my unit test is called saveA method), run it nontransactionally.

In the case of an exception when there is a transaction on the saveA method and the default propagation is REQUIRED, the data for both tables A and B will be rolled back

Let’s take a look at a transaction on saveA

This SUPPORTS(Joins a transaction if it currently exists)This rule

Let’s see if the propagation level on the saveA method is SUPPORTS

So in the database

You can see the same as without transaction

3. MANDATORY

  • Runs in the current transaction, if one currently exists
  • If there is currently no transaction, an exception is thrown, meaning that the parent method must have a transaction

Insert (a) (b) (b) (b) So it won’t go into storage.

If you declare a transaction in the saveA method and set it to REQUIRED, then saveB is executed using a transaction already opened by saveA, and an exception is rolled back as normal. That is (running in the current transaction if one exists)

4. REQUIRES_NEW

  • Create a new transaction and suspend the current transaction if one exists
  • This (REQUIRES_NEW) commits the transaction independently of the caller’s transaction, with a parent exception, and it commits normally

The results proved itREQUIRES_NEW commits the transaction independently, independent of the caller's transaction, with a parent (caller) exception, which also commits normally

5. NOT_SUPPORTED

  • Run in a non-transactional manner
  • Suspends the current transaction if it exists (the caller)

Look at the results

The result of this scenario is that ‘first insert A description’ and ‘second insert B description’ are not stored, but ‘first insert B description’ is saved successfully. The reason: SaveA has a transaction, but saveB does not use a transaction (NOT_SUPPORTED), so the first data of saveB is saved successfully, and then an exception is thrown. Then saveA detected that the exception transaction is rolled back, but because saveB is not in the transaction, only saveA’s INSERT is rolled back. In the end, only the first INSERT of saveB was successfully stored, and neither the saveA insert (which rolled back) nor the second INSERT of saveB (which never got there at all) was successfully stored.

6. NEVER

  • Run nontransactionally, throw an exception if a transaction is currently present, that is, the parent method must be transaction-freeBully ha ha ~

Take a look at the results:

7. NESTED

  • If a transaction exists, it will become a child of the parent transaction and will not commit until the parent transaction completes
  • If there is no transaction currently, create a new transaction
  • If it is abnormal, the parent can catch its exception without rolling back and commit normally
  • But if the parent exception, it must be rolled back, which is also the sumREQUIRES_NEWREQUIRES_NEW is that if the parent is abnormal, it will still execute the transaction.

Two points to note here:

  • REQUIRES_NEW

REQUIRES_NEW creates a new transaction that is unrelated to the original one, while NESTED transactions (called a child transaction) are started when a current transaction exists (called a parent transaction). In the NESTED case of REQUIRES_NEW, the original transaction was rolled back without affecting the newly started transaction. In the case of REQUIRES_NEW, a new transaction was created and isolated from each other.

  • And REQUIRED

In the case of REQUIRED, the called and the caller use the same transaction when a transaction exists. Therefore, when an exception occurs in the called, the transaction will be rolled back regardless of whether the caller catches the exception. In the case of NESTED, the caller can catch the exception. In this way, only the child transaction is rolled back, and the parent transaction is not affected

In this scenario, none of the data is stored in the database because when saveA fails, the parent transaction rolls back and the child transaction rolls back with it, comparing the example in REQUIRES_NEW to REQUIRES_NEW

You can see that there is no data (because the parent transaction has an exception rollback, the nested transaction also rolls back)

Move the exception to saveB

Move the exception to saveB and catch it in saveA

Look at the results

If you see the parent catch, you can commit the contents of the parent transaction normally, but the nested transaction must be rolled back.

By the end of this article, I’m determined to learn about distributed transactions. I’ve stumbled on this many times. Didn’t the ancients say “never fall over the same stone”? Isn’t it?