ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • @Transactional 과 Database Connection
    Spring Framework 2023. 8. 24. 00:01

    개요

    Spring에서 @Transational 어노테이션을 활용하여 데이터베이스와 통신할 때 보통 Connection Pool을 활용하곤 합니다.

    이때 하나의 트랜잭션은 하나의 Database Connection을 활용하는가? 문득 궁금해져 확인해보려 합니다.

    @Transactional과 Database Connection은 어떤 관계가 있을까요?

     

    준비 - 매우 간단한 Save

    @Service
    class TestService(
       private val testRepository: TestRepository,
    ) {
    
        @Transactional
        fun test(){
            testRepository.save(TestEntity(name = "test"))
        }
    }

     

    테스트 환경 

    • Spring Boot 3.x
    • Jpa
    • H2
    • Hikari Pool

     

    @Transactional이 선언된 코드 디버그

    PlatformTransactionManager의 구현체인 AbstractPlatformTransactionManager Class의 getTransaction 메서드에 디버깅 포인트를 걸어보았습니다.

     

    이제 Transcation을 얻어오는 것을 탐지할 수 있습니다.

     

    Spring에서 Transaction Manager는 위의 PlatformTransactionManager처럼 트랜잭션을 추상화하는 역할을 가지고 있습니다.

    또한 트랜잭션 내의 리소스를 동기화를 수행합니다.

    같은 트랜잭션을 유지하기 위해서는 파라미터로 Connection을 넘겨줘야 하지만 이러면 중복코드가 생성됩니다.

     

    이 역할을 TransactionSynchronizationManager가 수행합니다.

    내부적으로 ThreadLocal을 사용하기 때문에 멀티스레드 상황에서 Connection을 동기화할 수 있습니다.

     

     

    또는 HikariPool Class의 getConnection 메서드에 디버깅 포인트를 걸어서 Connection을 확인할 수 있습니다.

     

    여기서 PoolEntry 클래스가 실제로 connection을 가지고 있습니다.

    conn0을 사용하고 있습니다.

     

     

    만약 트랜잭션을 수행하다가 다른 트랜잭션을 다시 호출하는 경우는 어떻게 될까?

    @Service
    class InnerTestService(
        private val testRepository: TestRepository,
    ) {
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        fun test(){
            testRepository.save(TestEntity(name = UUID.randomUUID().toString()))
        }
    }

    Propagation.Requires_New를 활용하여 새로운 트랜잭션을 만들어냅니다.

    디버깅을 해보면 다른 connection을 사용하고 있습니다.

     

    하지만 서로다른 트랜잭션이더라도 inner 트랜잭션에서 롤백이 발생하면, outer 트랜잭션에서도 롤백이 발생합니다.

    (같은 쓰레드를 공유하고 있기 때문)

     

    1개의 Database Save만 수행해서 그런것 아닐까요?

    @Transactional(propagation = Propagation.REQUIRES_NEW)
        fun test(){
            repeat(10){
                testRepository.save(TestEntity(name = UUID.randomUUID().toString()))
            }
        }

    10번 저장을 수행해보아도 connection 1개만 사용합니다.

     

    데드락이 발생할 수 있는 경우 탐지법

    1. HikariCP의 Maximum Pool Size을 1로 설정한 다음 1건씩 Query를 실행해 봅니다.
      만약 정상적으로 실행되지 않고, connection timeout과 같은 에러가 발생한다면 Dead lock 발생 가능성이 있는 코드입니다.

    생각해 보면 당연한 게 1번 조건에 해당하는 Query가 N개가 있다고 가정했을 때 서로 connection을 하나씩 점유하고 있다면 dead lock의 발생 조건을 만족합니다.

     

     

    참고자료

    https://techblog.woowahan.com/2664/

    https://zzang9ha.tistory.com/414

    https://bimmm.tistory.com/51

     

    댓글

Designed by Tistory.