I am not good at writing. If there is something wrong, please point out it generously. Thank you very much.
To learn things, we need to integrate knowledge with practice. If we only know the theory but never practice it, we will not have a solid grasp of it. It is estimated that we will forget it in a few days. Next, we will practice together to learn the propagation properties of Spring transactions.
Propagation attribute
The propagation attribute defines the processing behavior when one transaction method encounters another transaction method. There are seven behaviors in total. The definitions are as follows
Transmissible | value | describe |
---|---|---|
PROPAGATION_REQUIRED | 0 | Support the current transaction. If not, create a new transaction. |
PROPAGATION_SUPPORTS | 1 | Supports the current transaction. If not, it will not run in a transactional way. |
PROPAGATION_MANDATORY | 2 | Support the current transaction. If there is no current transaction, throw an exception. |
PROPAGATION_REQUIRES_NEW | 3 | Whether there is a current transaction or not, a new transaction will be created |
PROPAGATION_NOT_SUPPORTED | 4 | Transaction is not supported. If there is currently a transaction, suspend the transaction and not run it as a transaction. |
PROPAGATION_NEVER | 5 | Transaction is not supported, throw exception if any |
PROPAGATION_NESTED | 6 | If there is a current transaction, start a new one in the current transaction |
In fact, if we only look at the concept, it is very straightforward to explain the role of each communication. At this time, we will use specific examples to demonstrate the behavior under each communication attribute.
In this demonstration, we use H2 database, which is used in memory, so it's just right for us to demonstrate the transaction effect. We don't need to do other configurations. We create a new table. Just put the following statement in the schema.sql file. When the spring boot program starts, it will automatically create such a table for us in memory.
CREATE TABLE FOO (ID INT IDENTITY, BAR VARCHAR(64));
Before demonstration, we will define two classes FooService and BarService. We use the methods in BarService to call the methods in FooService.
Environmental preparation
Before the transaction demonstration, it can be divided into the following situations. According to the arrangement and combination, we can get the following eight situations
- Caller: is there any transaction?
- Caller: is there any exception?
- Callee: whether there is a transaction * * (this is controlled by the propagation attribute) * * so it is not in the permutation
- Callee: abnormal or not
Does the caller have a transaction | Is the caller abnormal | Is the callee abnormal? |
---|---|---|
Yes | Yes | Yes |
Yes | Yes | nothing |
Yes | nothing | Yes |
Yes | nothing | nothing |
nothing | Yes | Yes |
nothing | Yes | nothing |
nothing | nothing | Yes |
nothing | nothing | nothing |
Exception class
RollbackException is an exception class defined by ourselves.
@Service public class BarServiceImpl implements BarService{ @Autowired private FooService fooService; // Promotion? Required demo no transaction @Override public void testRequiredNoTransactional() throws RollbackException { fooService.testRequiredTransactional(); } }
caller
There are two methods defined in BarService, one with transaction and the other without transaction
// Affairs @Override @Transactional(rollbackFor = Exception.class) public void hasTransactional() throws RollbackException { } // No transaction @Override public void noTransactional() throws RollbackException { }
Next, we will learn the transaction propagation attribute according to the eight situations defined above in Russia.
PROPAGATION_REQUIRED
Under this propagation property, whether the callee creates a new transaction depends on whether the caller brings the transaction.
To understand the characteristics of this propagation property, we need to demonstrate two examples of the above eight situations.
Does the caller have a transaction | Is the caller abnormal | Is the callee abnormal? |
---|---|---|
nothing | nothing | Yes |
Yes | Yes | nothing |
- In the first case, when the callee throws an exception, if the inserted data cannot be queried, it means that the callee creates a new transaction when the caller has no transaction.
- In the second case, when the caller throws an exception, if the inserted data cannot be queried, it means that the callee will join the current transaction when the caller has a transaction.
Let's first look at the method example of the callee's class.
@Service public class FooServiceImpl implements FooService { @Autowired private JdbcTemplate jdbcTemplate; // REQUIRED propagation property - exception thrown by callee @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) public void testRequiredHasException() throws RollbackException { jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ("+Global.REQUIRED_HAS_EXCEPTION+")"); throw new RollbackException(); } // REQUIRED propagation property - no exception thrown by callee @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) public void testRequiredNoException() throws RollbackException { jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ("+Global.REQUIRED_NO_EXCEPTION+")"); } }
Let's take a look at an example of the caller method
@Service public class BarServiceImpl implements BarService{ @Autowired private FooService fooService; // Affairs @Override @Transactional(rollbackFor = Exception.class) public void hasTransactional() throws RollbackException { // The caller has transactions, throw exceptions the callee has no exceptions fooService.testRequiredNoException(); throw new RollbackException(); } // No transaction @Override public void noTransactional() throws RollbackException { // The caller has no transaction, and the callee does not throw exceptions. The callee has exceptions. fooService.testRequiredHasException(); } }
At this point, we query when the program is called
String noException = Global.REQUIRED_NO_EXCEPTION; String hasException = Global.REQUIRED_HAS_EXCEPTION; try { barService.noTransactional(); }catch (Exception e){ log.info("The first situation {}", jdbcTemplate .queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='"+hasException+"'", Long.class)); } try { barService.hasTransactional(); }catch (Exception e){ log.info("The second situation {}", jdbcTemplate .queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='"+noException+"'", Long.class)); }
View printed logs
2019-10-16 13:02:04.142 INFO 11869 --- [ main] c.e.t.t.TransactionApplication : Case 1 0 2019-10-16 13:02:04.143 INFO 11869 --- [ main] c.e.t.t.TransactionApplication : Second case 0
We see that we have not found the corresponding data, indicating that the data has been rolled back. At this time, we should understand that sentence to support the current transaction. If not, create a new transaction.
PROPAGATION_SUPPORTS
Whether the callee has a transaction depends entirely on the caller. If the caller has a transaction, then there is a transaction. If the caller has no transaction, then there is no transaction.
Let's use the above two examples for demonstration.
Does the caller have a transaction | Is the caller abnormal | Is the callee abnormal? |
---|---|---|
nothing | nothing | Yes |
Yes | Yes | nothing |
- The first case: when the callee throws an exception, if the data can still be queried, it means that the transaction has not been rolled back, and the callee has no transaction.
- The second case: if the caller throws an exception, if no data can be found, the two methods are in the same transaction.
The following is still an example
The callee only changes the propagation attribute in the @ Transactional annotation to Propagation.SUPPORTS
// SUPPORTS propagation property - exception thrown by callee @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS) public void testSupportsHasException() throws RollbackException { jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_HAS_EXCEPTION+"')"); throw new RollbackException(); } // SUPPORTS propagation property - no exception thrown by callee @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS) public void testSupportsNoException() throws RollbackException { jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_NO_EXCEPTION+"')"); }
The caller is the same as the above example call. Let's see the execution effect directly.
2019-10-16 13:50:27.738 INFO 12174 --- [ main] c.e.t.t.TransactionApplication : Case 1 2019-10-16 13:50:27.741 INFO 12174 --- [ main] c.e.t.t.TransactionApplication : Second case 0
We see that in the first case, the data is found, indicating that in the first case, the callee has no transaction. At this time, we should understand this sentence. Support the current transaction. If not, it will not run as a transaction.
PROPAGATION_MANDATORY
It's still these two examples.
Does the caller have a transaction | Is the caller abnormal | Is the callee abnormal? |
---|---|---|
nothing | nothing | Yes |
Yes | Yes | nothing |
- Case 1: because the caller has no transaction, exceptions should be thrown under this propagation attribute.
- The second case: the transaction of the callee is the same as that of the caller
Next is the code example of the callee
// MANDATORY propagation property - exception thrown by callee @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY) public void testMandatoryHasException() throws RollbackException { jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_HAS_EXCEPTION+"')"); throw new RollbackException(); } // MANDATORY propagation property - no exception thrown by callee @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY) public void testMandatoryNoException() throws RollbackException { jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.SUPPORTS_NO_EXCEPTION+"')"); }
The caller is the same as the above example call. Let's see the execution effect directly.
2019-10-16 13:58:39.178 ERROR 12317 --- [ main] c.e.t.t.TransactionApplication : org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory' 2019-10-16 13:58:39.276 INFO 12317 --- [ main] c.e.t.t.TransactionApplication : Case 1 0 2019-10-16 13:58:39.281 INFO 12317 --- [ main] c.e.t.t.TransactionApplication : Second case 0
We find that, as we speculate, the callee will not create a new transaction by himself. At this time, we should understand that this sentence supports the current transaction, and throw exceptions if there is no transaction at present.
PROPAGATION_REQUIRES_NEW
Under this propagation attribute, whether the caller has a transaction or not, the callee will create a new transaction.
Does the caller have a transaction | Is the caller abnormal | Is the callee abnormal? |
---|---|---|
nothing | nothing | Yes |
Yes | Yes | nothing |
- Case 1: the caller has no transaction, and the callee will create a new transaction, so no data can be found.
- The second situation: if the caller has a transaction, the callee will create a new transaction, so the exception thrown by the caller will not affect the callee, so the data can be found.
Next we show the code.
Caller
// Requests? New propagation property - exception thrown by callee @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW) public void testRequiresNewHasException() throws RollbackException { jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.REQUIRES_NEW_HAS_EXCEPTION+"')"); throw new RollbackException(); } // Requests? New propagation property - no exception thrown by the callee @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW) public void testRequiresNewNoException() throws RollbackException { jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.REQUIRES_NEW_NO_EXCEPTION+"')"); }
The caller's example is the same as above. Let's take a look at the implementation directly.
2019-10-16 16:29:20.296 INFO 15553 --- [ main] c.e.t.t.TransactionApplication : Case 1 0 2019-10-16 16:29:20.298 INFO 15553 --- [ main] c.e.t.t.TransactionApplication : Second case 1
We find that it is the same as our inference that the transaction of the caller has nothing to do with the transaction of the callee. At this point, we should understand that no matter whether there is a current transaction or not, there will be a new one.
PROPAGATION_NOT_SUPPORTED
The callee does not run as a transaction, regardless of whether the caller has a transaction or not
The same two examples
Does the caller have a transaction | Is the caller abnormal | Is the callee abnormal? |
---|---|---|
nothing | nothing | Yes |
Yes | Yes | nothing |
- The first situation: the callee will not have transactions, so the corresponding data can be found after throwing exceptions
- The second case: when the caller has transactions, the callee will also run in a transaction free environment, so we can still find the data.
Let's test our guess.
// Not supported propagation property - exception thrown by callee @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED) public void testNotSupportHasException() throws RollbackException { jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NOT_SUPPORTS_HAS_EXCEPTION+"')"); throw new RollbackException(); } // Not supported propagation property - no exception thrown by the callee @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED) public void testNotSupportNoException() throws RollbackException { jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NOT_SUPPORTS_NO_EXCEPTION+"')"); }
Then view the execution results
2019-10-16 16:38:35.065 INFO 15739 --- [ main] c.e.t.t.TransactionApplication : Case 1 2019-10-16 16:38:35.067 INFO 15739 --- [ main] c.e.t.t.TransactionApplication : Second case 1
We can see that we have found data in the last two cases. According to the demonstration effect, we can understand this sentence. Transaction is not supported. If there is a transaction at present, the transaction will be suspended and not run in transaction mode.
PROPAGATION_NEVER
If the caller has a transaction, the callee will throw an exception
Does the caller have a transaction | Is the caller abnormal | Is the callee abnormal? |
---|---|---|
nothing | nothing | Yes |
Yes | Yes | nothing |
This is not a demonstration. I believe you will understand that in the first case, we can find the data. In the second case, the exception is thrown because the caller carries the transaction.
PROPAGATION_NESTED
Under this propagation attribute, the transaction of the callee is a subset of the transaction of the caller.
Let's focus on the characteristics of NESTED's propagation properties.
Does the caller have a transaction | Explain |
---|---|
Yes | The callee will start a new transaction, which is a nested relationship with the caller transaction |
nothing | The callee will start a new business by himself |
We can use the following three examples to demonstrate what a nested transaction relationship is.
Does the caller have a transaction | Is the caller abnormal | Is the callee abnormal? |
---|---|---|
nothing | nothing | Yes |
Yes | Yes | nothing |
Yes | nothing | Yes |
- Case 1: if no data can be found, the callee will start a new transaction when the caller has no transaction.
- Second, if no data can be found, it indicates that the outer transaction can affect the inner transaction.
- Third, if data is found, it indicates that the inner transaction does not affect the outer transaction.
Next, we write the specific code.
// NESTED propagation properties - rollback transaction @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED) public void testNestedHasException() throws RollbackException { jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_HAS_EXCEPTION+"')"); // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); throw new RollbackException(); } // NESTED propagation properties - do not roll back transactions @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED) public void testNestedNoException() throws RollbackException { jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_NO_EXCEPTION+"')"); }
And then the next callers will be a little different.
@Override @Transactional() public void hasTransactionalNoException() throws RollbackException { // NESTED propagation attribute - the caller has transactions, the callee does not throw exceptions has exceptions jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('"+Global.NESTED_HAS_EXCEPTION_TWO+"')"); fooService.testNestedHasException(); }
Then perform the effect
2019-10-16 18:01:06.387 INFO 17172 --- [ main] c.e.t.t.TransactionApplication : Case 1 0 2019-10-16 18:01:06.389 INFO 17172 --- [ main] c.e.t.t.TransactionApplication : Second case 0 2019-10-16 18:01:06.390 INFO 17172 --- [ main] c.e.t.t.TransactionApplication : Third case 1
It can be seen that the essence of nested transactions is that the outer layer will affect the inner layer, and the inner layer will not affect the outer layer. The requirements "new" does not affect each other.
summary
Up to now, we have analyzed all seven kinds of communication attributes. From the beginning to the end of writing this article, we have also encountered some holes. Some of them don't know if they don't practice it by themselves. So I suggest the readers to practice and demonstrate all kinds of situations after reading this article. Only in this way can they get familiar with it.