Transaction Propagation
Transaction Propagation, νΈλμμ μ νλ νΈλμμ μ κ²½κ³μμ μ΄λ―Έ μ§νμ€μΈ νΈλμμ μ΄ μμ λ λλ μμ λ μ΄λ»κ² λμν κ²μΈκ°λ₯Ό κ²°μ νλ λ°©μμ μλ―Ένλ€.
@Transactional
μ propagation
μμ±μ ν΅ν΄ νΌνΈμΆ νΈλμμ
μ
μ₯μμ νΈμΆν μͺ½μ νΈλμμ
μ κ·Έλλ‘ μ¬μ©ν μλ μκ³ , μλ‘κ² νΈλμμ
μ μμ±ν μ λ μλ€.
Propagation μ’ λ₯
Transaction Propagation μλ μ΄ 7κ°μ§ μ ν μμ±μ΄ μ‘΄μ¬νλ€.
REQUIRED
SUPPORTS
MANDATORY
REQUIRES_NEW
NOT_SUPPORTED
NEVER
NESTED
Physical Transaction & Logical Transaction
Physical Transaction
- 물리μ νΈλμμ μ μ€μ νΈλμμ μ΄ μ€νλλ μ€λ λλ₯Ό μλ―Ένλ€.
Logical Transaction
- λ
Όλ¦¬μ νΈλμμ
μ μ μΈμ νΈλμμ
μ΄ μ μ©λ λͺ¨λ λ©μλλ₯Ό μλ―Ένλ€.
- μ¦, μΈλΆ νΈλμμ κ³Ό λ΄λΆ νΈλμμ μ λ Όλ¦¬μ λ²μλ λ 립μ μ΄λ€.
- λ‘€λ°± μνλ₯Ό κ°λ³μ μΌλ‘ κ²°μ ν μ μκ³ μ΄λ 물리μ νΈλμμ
μ 맀νλλ€.
- λ°λΌμ, λ΄λΆ νΈλμμ μ€μ μΌλ‘ μΈν΄ μΈλΆ νΈλμμ μ 컀λ°μ μν₯μ λ―ΈμΉ μ μλ€.
- λ΄λΆ νΈλμμ
μΌλ‘ μΈν λ‘€λ°±μ μΈλΆ νΈλμμ
μ
μ₯μμ μμν μ μλ λ‘€λ°±μ΄λ€.
UnexpectedRollbackException
μ λ°μμν΄μΌλ‘μ¨ μΈλΆ νΈλμμ μ΄ κ·Έ μ¬μ€μ μ μ μλ€.
REQUIRED (DEFAULT)
@Nested
class testRequired {
@Test
@DisplayName("λΆλͺ¨ νΈλμμ
μ΄ μλ Required")
void withParent() {
final var actual = parentService.required(ChildService::required);
log.info("transactions : {}", actual);
assertThat(actual)
.hasSize(1);
}
@Test
@DisplayName("λΆλͺ¨ νΈλμμ
μ΄ μλ Required")
void withoutParent() {
final var actual = parentService.nonTransactional(ChildService::required);
log.info("transactions : {}", actual);
assertThat(actual)
.hasSize(1);
}
}
- λΆλͺ¨ νΈλμμ μ΄ μλ€λ©΄ λΆλͺ¨ νΈλμμ μ ν©λ₯νλ€.
- λΆλͺ¨ νΈλμμ μ΄ μλ€λ©΄ μλ‘μ΄ νΈλμμ μ μμ±νλ€.
λΆλͺ¨ νΈλμμ μ΄ μλ κ²½μ°
ParentService : Parent Transaction Active : π’
ChildService : Child Transaction Active : π’
PropagationTest : transactions : [ParentService]
λΆλͺ¨ νΈλμμ μ΄ μλ κ²½μ°
ParentService : Parent Transaction Active : π«
ChildService : Child Transaction Active : π’
PropagationTest : transactions : [ChildService]
SUPPORTS
@Nested
class testSupports {
@Test
@DisplayName("λΆλͺ¨ νΈλμμ
μ΄ μλ Supports")
void withParent() {
final var actual = parentService.required(ChildService::supports);
log.info("transactions : {}", actual);
assertThat(actual)
.hasSize(1);
}
@Test
@DisplayName("λΆλͺ¨ νΈλμμ
μ΄ μλ Supports")
void withoutParent() {
final var actual = parentService.nonTransactional(ChildService::supports);
log.info("transactions : {}", actual);
assertThat(actual)
.hasSize(1);
}
}
- νΈλμμ
μ΄ νμν μ²λ¦¬λ μμ§λ§ μ€ν¨νλ©΄ λ‘€λ°±ν΄μΌ νλ κ²½μ° μ¬μ©νλ μ ν μμ±μ΄λ€.
- λΆλͺ¨ νΈλμμ μ΄ μλ€λ©΄ λΆλͺ¨ νΈλμμ μ ν©λ₯νλ€.
- λΆλͺ¨ νΈλμμ μ΄ μλ€λ©΄ νΈλμμ μ μμ±νμ§ μλλ€.
λΆλͺ¨ νΈλμμ μ΄ μλ κ²½μ°
ParentService : Parent Transaction Active : π’
ChildService : Child Transaction Active : π’
PropagationTest : transactions : [ParentService]
λΆλͺ¨ νΈλμμ μ΄ μλ κ²½μ°
ParentService : Parent Transaction Active : π«
ChildService : Child Transaction Active : π«
MANDATORY
@Nested
class testMandatory {
@Test
@DisplayName("λΆλͺ¨ νΈλμμ
μ΄ μλ Mandatory")
void withParent() {
final var actual = parentService.required(ChildService::mandatory);
log.info("transactions : {}", actual);
assertThat(actual)
.hasSize(1);
}
@Test
@DisplayName("λΆλͺ¨ νΈλμμ
μ΄ μλ Mandatory")
void withoutParent() {
assertThatThrownBy(() -> parentService.nonTransactional(ChildService::mandatory))
.isInstanceOf(IllegalTransactionStateException.class)
.hasMessage("No existing transaction found for transaction marked with propagation 'mandatory'");
}
}
- λΆλͺ¨ νΈλμμ
μ μν΄ νμ νΌνΈμΆλμ΄μΌ νλ μ ν μμ±μ΄λ€.
- λΆλͺ¨ νΈλμμ μ΄ μλ€λ©΄ λΆλͺ¨ νΈλμμ μ ν©λ₯νλ€.
- λΆλͺ¨ νΈλμμ μ΄ μλ€λ©΄ μμΈλ₯Ό λ°μμν¨λ€.
λΆλͺ¨ νΈλμμ μ΄ μλ κ²½μ°
ParentService : Parent Transaction Active : π’
ChildService : Child Transaction Active : π’
PropagationTest : transactions : [ParentService]
λΆλͺ¨ νΈλμμ μ΄ μλ κ²½μ°
No existing transaction found for transaction marked with propagation 'mandatory'
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
REQUIRES_NEW
@Nested
class testRequiredNew {
@Test
@DisplayName("λΆλͺ¨ νΈλμμ
μ΄ μλ Requires_New")
void withParent() {
final var actual = parentService.required(ChildService::requiresNew);
log.info("transactions : {}", actual);
assertThat(actual)
.hasSize(2);
}
@Test
@DisplayName("λΆλͺ¨ νΈλμμ
μ΄ μλ Requires_New")
void withoutParent() {
final var actual = parentService.nonTransactional(ChildService::requiresNew);
log.info("transactions : {}", actual);
assertThat(actual)
.hasSize(1);
}
@Test
@DisplayName("λΆλͺ¨ νΈλμμ
μ΄ μμΈλ₯Ό λ°μνλ Requires_New")
void withRollback() {
assertThat(parentService.findAll()).hasSize(0);
assertThatThrownBy(() -> parentService.exception(ChildService::requiresNew))
.isInstanceOf(RuntimeException.class);
assertThat(parentService.findAll()).hasSize(1);
}
}
- 무쑰건 μλ‘μ΄ νΈλμμ μ μμ±νλ€.
- νμ 물리μ νΈλμμ μ μλ‘ μμ±νκΈ° λλ¬Έμ λ΄λΆ νΈλμμ μ΄ μΈλΆ νΈλμμ μ μν₯μ μ£Όμ§ μλλ€.
λΆλͺ¨ νΈλμμ μ΄ μλ κ²½μ°
ParentService : Parent Transaction Active : π’
ChildService : Child Transaction Active : π’
PropagationTest : transactions : [ParentService, ChildService]
λΆλͺ¨ νΈλμμ μ΄ μλ κ²½μ°
ParentService : Parent Transaction Active : π«
ChildService : Child Transaction Active : π’
PropagationTest : transactions : [ChildService]
λΆλͺ¨ νΈλμμ μ΄ μμΈλ₯Ό λ°μμν¬ κ²½μ°
ChildService : Child Transaction Active : π’
PropagationTest : transactions : [ChildService]
NOT_SUPPORTED
@Nested
class testNotSupported {
@Test
@DisplayName("λΆλͺ¨ νΈλμμ
μ΄ μλ Not_Supported")
void withParent() {
final var actual = parentService.required(ChildService::notSupported);
log.info("transactions : {}", actual);
assertThat(actual)
.hasSize(2);
}
@Test
@DisplayName("λΆλͺ¨ νΈλμμ
μ΄ μλ Not_Supported")
void withoutParent() {
final var actual = parentService.nonTransactional(ChildService::notSupported);
log.info("transactions : {}", actual);
assertThat(actual)
.hasSize(1);
}
}
- λΆλͺ¨ νΈλμμ μ΄ μλ€λ©΄ ν΄λΉ νΈλμμ μ 보λ₯μν€κ³ νΈλμμ μ΄ μλ μνλ‘ μ²λ¦¬λ₯Ό μννλ€.
- λΆλͺ¨ νΈλμμ μ΄ μλ€λ©΄ νΈλμμ μ μμ±νμ§ μλλ€.
λΆλͺ¨ νΈλμμ μ΄ μλ κ²½μ°
ParentService : Parent Transaction Active : π’
ChildService : Child Transaction Active : π«
PropagationTest : transactions : [ParentService]
λΆλͺ¨ νΈλμμ μ΄ μλ κ²½μ°
ParentService : Parent Transaction Active : π«
ChildService : Child Transaction Active : π«
NEVER
@Nested
class testNever {
@Test
@DisplayName("λΆλͺ¨ νΈλμμ
μ΄ μλ Never")
void withParent() {
assertThatThrownBy(() -> parentService.required(ChildService::never))
.isInstanceOf(IllegalTransactionStateException.class)
.hasMessage("Existing transaction found for transaction marked with propagation 'never'");
}
@Test
@DisplayName("λΆλͺ¨ νΈλμμ
μ΄ μλ Never")
void withoutParent() {
final var actual = parentService.nonTransactional(ChildService::never);
log.info("transactions : {}", actual);
assertThat(actual)
.hasSize(1);
}
}
- νΈλμμ
μ μ¬μ©νμ§ μλλ‘ κ°μ νλ μμ±μ΄λ€.
- λΆλͺ¨ νΈλμμ μ΄ μλ€λ©΄ μμΈλ₯Ό λ°μμν¨λ€.
λΆλͺ¨ νΈλμμ μ΄ μλ κ²½μ°
Existing transaction found for transaction marked with propagation 'never'
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
λΆλͺ¨ νΈλμμ μ΄ μλ κ²½μ°
ParentService : Parent Transaction Active : π«
ChildService : Child Transaction Active : π«
NESTED
@Nested
class testNested {
@Test
@DisplayName("λΆλͺ¨ νΈλμμ
μ΄ μλ Nested")
void withParent() {
assertThatThrownBy(() -> parentService.required(ChildService::nested))
.isInstanceOf(NestedTransactionNotSupportedException.class)
.hasMessage("JpaDialect does not support savepoints - check your JPA provider's capabilities");
}
@Test
@DisplayName("λΆλͺ¨ νΈλμμ
μ΄ μλ Nested")
void withoutParent() {
final var actual = parentService.nonTransactional(ChildService::nested);
log.info("transactions : {}", actual);
assertThat(actual)
.hasSize(1);
}
}
- λΆλͺ¨ νΈλμμ
μ΄ μλ€λ©΄ JDBC Savepoint κΈ°λ₯μ μ΄μ©ν΄ λ΄λΆμ μ€μ²© νΈλμμ
μ μμ±νλ€.
- μ€μ²© νΈλμμ μμ λ‘€λ°± λ°μ μ, ν΄λΉ μ€μ²© νΈλμμ μμ μμ κΉμ§λ§ λ‘€λ°±λλ€.
- μ€μ²© νΈλμμ μ λΆλͺ¨ νΈλμμ μ΄ μ»€λ°λ λ κ°μ΄ 컀λ°λλ€.
- λ¨μΌ 물리μ νΈλμμ μ μ¬λ¬ Savepoint λ₯Ό λμ΄ λ΄λΆ νΈλμμ μ λ‘€λ°±μ΄ μ€νλμ΄λ μΈλΆ νΈλμμ μμλ 물리μ νΈλμμ μ κ³μν μ μλ€.
- λΆλͺ¨ νΈλμμ μ΄ μλ€λ©΄ μλ‘μ΄ νΈλμμ μ μμνλ€.
λΆλͺ¨ νΈλμμ μ΄ μλ κ²½μ°
JpaDialect does not support savepoints - check your JPA provider's capabilities
org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities
- JPA μ κ²½μ° Dirty Checking μ ν΅ν μ°κΈ° μ§μ° λλ¬Έμ μ€μ²© νΈλμμ μ μμ±ν μ μμ΄ μ§μνμ§ μλλ€.
λΆλͺ¨ νΈλμμ μ΄ μλ κ²½μ°
ParentService : Parent Transaction Active : π«
ChildService : Child Transaction Active : π’
PropagationTest : transactions : [ChildService]