1. @Transactional Propaction
스프링에서 트랜젝션을 설정하는 @Transactional 에는 전파레벨을 설정할 수 있다.
@Transactional(propagation = Propagation.<선택>)
- PROPAGATION_REQUIRED
- 디폴트 값.
- 부모 트랜잭션이 있을 때 부모 트랜잭션에 참여. 없으면 새로운 트랜잭션 시작.
- 자식 트랜잭션에서 예외가 발생하면 부모 트랜잭션까지 모두 롤백.
- PROPAGATION_SUPPORTS
- 부모 트랜잭션이 있을 때 부모 트랜잭션에 참여. but 없으면 트랜잭션 없이 동작.
- PROPAGATION_MANDATORY
- 부모 트랜잭션이 있을 때 부모 트랜잭션에 참여. 부모 트랜잭션이 없다면 Exception 발생.
- PROPAGATION_REQUIRES_NEW
- 부모 트랜잭션에 상관없이 무조건 새로운 트랜잭션 실행. 부모 트랜잭션이 있다면 자식 트랜잭션 메서드 전에 부모 트랜잭션을 suspend 시키고, 자식 트랜잭션 메서드가 끝날 때 까지 부모 트랜잭션을 잠시 대기시킨 후 자식 트랜젝션이 끝나면 부모 트랜잭션을 다시 resume 한다.
- 새로운 트랜잭션이기 때문에, 자식 트랜잭션의 롤백은 부모 트랜잭션에 전파되지 않는다.
- PROPAGATION_NOT_SUPPORTED
- 부모 트랜잭션에 상관없이 무조건 트랜잭션 없이 동작. 부모 트랜잭션이 있다면 이 트랜잭션(non-transcation) 메서드를 시작하기 전 부모 트랜잭션을 suspend 시키고, 끝난 후 다시 resume 한다.
- PROPAGATION_NEVER
- 부모 트랜잭션에 상관없이 무조건 트랜잭션 없이 동작. 부모 트랜잭션이 존재하면 Exception 발생.
- PROPAGATION_NESTED
- 자식 트랜잭션에서 롤백이 발생했을 때, 부모 트랜잭션이 자식 트랜잭션에서 잡아둔 savepoint 까지 롤백한다. (savepoint 를 지원하는 DB 에서만 동작한다.)
- 부모 트랜잭션이 없으면 REQUIRED 와 같다.
- 부모 트랜잭션이 있다면 중첩 트랜잭션으로 자식 트랜잭션을 새로 생성한다. 위와 다른 점은 중첩으로, 독립적으로 실행한다는 것. 즉 위와는 다르게, 부모 트랜잭션을 suspend, resume 하지 않는다.
- JtaTransactionManager 로 트랜잭션 관리를 할 때에는 안되고, DataSourceTransactionManager 를 직접 사용할 때에만 동작한다. 아니면 아래와 같은 에러 발생. (SAVEPOINT 가 있어야 하기 때문에 JPA 단에서 추상화해서 처리할 수 없기 때문이 아닐까.)
JpaDialect does not support savepoints - check your JPA provider's capabilities
트랜잭션 전파레벨에 대한 글
https://techblog.woowahan.com/2606/
1. 부모, 자식 트랜잭션이 한 트랜잭션에서 일어났으면 좋겠고, (REQUIRED) (PROPAGATION_REQUIRES_NEW 는 완전히 독립적임)
2. 자식 트랜잭션 메서드에서 발생한 예외를 바로 처리하지 않고 (롤백 발생)
3. 부모 트랜잭션 메서드에서 예외처리를 적절하게 한다면
부모 트랜잭션은 커밋될까 ? -> NO
@Transcation 에서는 PROPAGATION_REQURED 로 하나의 트랜잭션으로 동작한다 하더라도, 자식 트랜잭션이 끝날 때 마다 완료처리를 한다.
@Transcation 에서 Exception 이 발생하면, [rollback-only] 마킹을 한다.
이 마킹은, 부모 트랜잭션에서 예외를 잡아 처리했다 하더라도, 부모 트랜잭션 메서드를 최종 커밋할때 마킹 떄문에 롤백하게 된다.
=> 자식 트랜잭션중에 하나라도 Exception 이 발생하면 전체 트랜잭션이 롤백된다.
globalRollbackOnParticipationFailure 주석
결국 내부 트랜잭션의 결과가 외부 트랜잭션의 결과에 영향을 끼치지 않게 하는 방법이 무엇이 있을지 생각해보자.
- PROPAGATION_REQUIRES_NEW 사용 : 독립적인 트랜잭션을 만들 수 있지만, 부모 트랜잭션은 일시정지된다.
- PROPAGATION_NESTED 사용 : 중첩 트랜잭션으로 독립적인 트랜잭션을 만들 수 있지만, DataSourceTransactionManager 를 직접 사용해야 한다.
- AbstractPlatformTransactionManager.globalRollbackOnParticipationFailure 디폴트 값이 true 인데 false 로 바꾸기 : 이러면 자식 트랜잭션이 예외로 롤백이 되어도 부모 트랜잭션은 계속 진행할지 말지 결정할 수 있다. 하지만, 데이터 액세스 실패 후에도 모든 참여 리소스가 트랜잭션 커밋을 계속할 수 있음이 보장되어야 한다. (솔직히 무슨말인지 잘 모르겠다.)
2 스프링 부트 앱에 초기화 코드를 넣는 방법
스프링 부트 시작하자 마자 무언가를 하고 싶다.
그런데 생성된 빈들을 사용하고 싶다.
방법은 세가지가 있다.
1. commandLineRunner
@Bean
fun myRunner(testController: TestController): CommandLineRunner {
return CommandLineRunner { println("test : $testController") }
}
또는
@Component
class MyRunner : CommandLineRunner {
override fun run(vararg args: String?) {
//TODO
}
}
2. ApplicationRunner
@Bean
fun myRunner(testController: TestController): ApplicationRunner {
return ApplicationRunner { println("test : $testController") }
}
또는
class Test: ApplicationRunner {
override fun run(args: ApplicationArguments?) {
TODO("Not yet implemented")
}
}
3. @EventListener(ApplicationReadyEvent::class)
@EventListener(ApplicationReadyEvent::class)
fun init() {
println("go")
}
스프링 부트가 스프링 컨텍스트를 띄우면서 발생하는 이벤트를 잡아서 처리하는 방식이다.
스프링 부트가 컨텍스트를 띄울 때 발생시키는 이벤트의 종류에는 SpringApplicationEvent 클래스를 상속한 7가지 이벤트가 있다.
그리고, 그 컨텍스트를 띄우는 코드는 아래 메서드이고, 안을 보면 이벤트를 퍼블리시하는 과정을 볼 수 있다.
SpringApplication.ConfigurableApplicationContext
안을 잘 살펴보면,
ApplicationStartedEvent -> ApplicationRunner -> CommandLineRunner -> ApplicationReadyEvent
순으로 실행됨을 알 수 있다.
위 순서로 쓴 대로 부트에 추가되었으며, 가장 최근인 @EventListener 방식을 주로 쓰기로 하고,
스프링 나름의 준비가 모두 된 후 ApplicationReadyEvent 에서 내가 돌리고 싶은 초기화 코드를 쓰는게 좋아 보인다.
https://www.youtube.com/watch?v=f017PD5BIEc
'TIL' 카테고리의 다른 글
TIL) associate 시리즈 (0) | 2022.02.19 |
---|---|
TIL) Enum, Memory Commit & OverCommit (0) | 2022.02.17 |
TIL) TINYINT/BIT, Spring Content-type, 코드를 DB 에서 읽어와서 처리할 때 resource (0) | 2022.02.15 |
TIL) @SqlResultSetMapping, Kotlin-Fold, Reduce (0) | 2022.02.05 |
TIL) cherry-pick 외 git 각종 취소 명령어들, ORIG_HEAD (0) | 2022.02.03 |