NOTE
Spring Transaction Propagation
1. What transaction propagation is. 1.1. Propagation behaviors including REQUIRED, SUPPORTS, MANDATORY, REQUIRES_NEW, NOT_SUPPORTED, and NEVER. 1.2. Notes. 2. References.
This is a historical learning note and may contain outdated or incomplete understanding.
1. What It Is
Transaction propagation behavior is an enhancement that Spring provides on top of database transactions. It describes how a transaction propagates when a method annotated with a particular propagation behavior is nested inside another method.
Usually, we place transactions on service methods, and they take effect when DAO methods are called. But how should we define the case where a method in Service A calls a method in Service B?
1.1. What Propagation Behaviors Are There?
1.1.1. PROPAGATION_REQUIRED
Support the current transaction. If there is no current transaction, create a new one. If a transaction exists, join the current transaction. This is the most common choice.
- ServiceA
@Transactional(PROPAGATION_REQUIRED)
methodA
{
doSomething();
ServiceB.methodB();
}
- ServiceB
methodB
{
doSomethingToo();
}
In this example, methodA and methodB run in the same transaction. As long as an exception occurs in one place, the transaction is rolled back directly.
1.1.1.1. Note
If methodA in ServiceA looks like this:
methodA
{
doSomething();
try
{
ServiceB.methodB();
}catch (Exception e)
....
}
Then when methodB throws an exception, the transaction is first marked as needing rollback, but when control returns to methodA, the exception is caught again. This creates a situation where the transaction needs to roll back but cannot roll back normally.
It throws a transaction has been marked as rollback only exception.
The solution is to put methodB in PROPAGATION_REQUIRES_NEW.
1.1.2. PROPAGATION_SUPPORTS
Support the current transaction. If there is no current transaction, execute non-transactionally.
1.1.3. PROPAGATION_MANDATORY
Support the current transaction. If there is no current transaction, throw an exception.
1.1.4. PROPAGATION_REQUIRES_NEW
Create a new transaction. If a current transaction exists, suspend it. If there is no current transaction, create a new one. If a transaction exists, create another new transaction and execute within that new transaction.
- ServiceA
@Transactional(PROPAGATION_REQUIRED)
methodA
{
doSomething();
ServiceB.methodB();
}
- ServiceB
@Transactional(PROPAGATION_REQUIRES_NEW)
methodB
{
doSomethingToo();
}
In the example above, methodA runs in one transaction. When it calls methodB, methodB opens a new transaction and runs in it. If methodB throws an exception, only methodB is rolled back. If an exception is thrown in methodA after it calls methodB, only methodA is rolled back.
1.1.5. PROPAGATION_NOT_SUPPORTED
Execute non-transactionally. If a current transaction exists, suspend it.
1.1.6. PROPAGATION_NEVER
Execute non-transactionally. If a current transaction exists, throw an exception.
1.2. Notes
1.2.1. One Transaction Proxy Class Is Created for the Same Class
- ServiceA
@Transactional(PROPAGATION_REQUIRED)
methodA
{
doSomething();
methodA2();
}
@Transactional(PROPAGATION_REQUIRED_NEW)
methodA2
{
doSomething();
ServiceB.methodB();
}
In the example above, the annotation on methodA2 does not take effect, because dynamic proxying creates only one transaction proxy class for the same class.
1.2.2. Transaction Already Marked as Rollback
- ServiceA
@Transactional(PROPAGATION_REQUIRED)
methodA
{
doSomething();
try
{
serviceB.methodB();
}catch (Exception e)
{
e.printStackTrance();
}
}
- ServiceB
@Transactional(PROPAGATION_REQUIRED)
methodB
{
throw new RuntimeExecption("serviceB methodB exp..");
}
The code above throws a Transaction already marked as rollback exception. The reason is that ServiceA.methodA first starts a transaction. Because the propagation behavior of ServiceB.methodB is PROPAGATION_REQUIRED, it does not create a new transaction; it directly joins the transaction of ServiceA.methodA.
ServiceB.methodB throws an exception, so this transaction is marked as pending rollback. However, ServiceA.methodA catches the exception and does not throw it outward. This creates a contradiction.
The transaction is clearly marked as pending rollback, but no exception is thrown. So should it roll back or not?
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub