트랜잭션

Mr.Sir·2022년 7월 27일
0

Develop

목록 보기
1/3

필자는 유독 DB 관련 이슈들을 자주 접하게 되는 느낌이 있다.(기분탓 아니고)

이전에 DB 트랜잭션 관련된 이슈들을 접한 후 정리해놔야 겠다고 마음먹었는데

드디어 블로그에 포스팅 하는겸 먼저 가물가물해지는 트랜잭션에 대해 찾아봤다.


트랜잭션 경계 설정 전략

일반적으로 트랜잭션의 시작과 종료는 service 레이어 내부 메소드에 달려있다.

트랜잭션의 경계를 설정하는 방법으로는, 크게 PlatformTransactionManager를 사용하여 트랜잭션을

코드를 통해 임의로 지정하는 방법과 AOP를 이용하여 지정하는 방법으로 나뉜다.

이 중에서 AOP를 활용한 @Transactional 어노테이션이 주로 사용된다.

선언적 트랜잭션에 경계를 설정할 수 있는 이유는 위에서 설명한 프록시 객체 덕분이다.

트랜잭션은 대부분 그 성격이 비슷하기 때문에 적용 대상 별로 일일이 선언하지 않고 일괄적으로 설정하는 편이 좋다. 따라서 특정 부가 기능을 임의의 타겟 객체에 부여할 수 있는 AOP가 주로 사용된다.

// JPA를 사용한 트랜잭션 코드

//Defalut Propagation : REQUIRED
@Transactional
public void invoke() {
    System.out.println("*** invoke start");
    insert1();
    insert2();
    System.out.println("*** invoke end");
}

//Defalut Propagation : REQUIRED
public void insert1() {
    bookRepository.save(new Book("오브젝트"));
}

//Defalut Propagation : REQUIRED
public void insert2() {
    bookRepository.save(new Book("토비의 스프링"));
}

위 코드에서 invoke() 메소드를 호출한 로그는 아래와 같다.

DEBUG **JpaTransactionManager** : Creating new transaction with name [dev.highright96.springstudy.transaction.BookServiceImpl.invoke]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
DEBUG **JpaTransactionManager** : Opened new EntityManager [SessionImpl(2076486718<open>)] for JPA transaction
DEBUG **JpaTransactionManager** : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@351fadfa]
*** invoke start
DEBUG **JpaTransactionManager** : Found thread-bound EntityManager [SessionImpl(2076486718<open>)] for JPA transaction
DEBUG **JpaTransactionManager** : Participating in existing transaction
Hibernate: insert into book (isbn, flag, name) values (null, ?, ?)
DEBUG **JpaTransactionManager** : Found thread-bound EntityManager [SessionImpl(2076486718<open>)] for JPA transaction
DEBUG **JpaTransactionManager** : Participating in existing transaction
Hibernate: insert into book (isbn, flag, name) values (null, ?, ?)
*** invoke end
DEBUG **JpaTransactionManager** : Initiating transaction commit
DEBUG **JpaTransactionManager** : Committing JPA transaction on EntityManager [SessionImpl(2076486718<open>)]
DEBUG **JpaTransactionManager** : Closing JPA EntityManager [SessionImpl(2076486718<open>)] after transaction

로그를 살펴보면 JpaTransactionManager가 트랜잭션 관리를 하는 것을 알 수 있다.

  • invoke start
    • invoke() 메소드가 시작되기 전에 DB 커넥션을 얻는다.
  • Participating in existing transaction
    • invoke() 메소드 내에서 insert1() 과 insert2() 에서 실행되는 트랜잭션은 기존 트랜잭션에 참여하며, 기본 트랜잭션 전파 설정은 REQUIRED이다.
  • invoke end
    • invoke() 메소드가 실행된 이후, 트랜잭션을 커밋하고 커넥션을 반환한다.

여기서 트랜잭션이 시작하는 지점은 invoke() 메소드에 대한 프록시 메소드 내부이므로, 다음 순서대로 호출 경계가 설정된다.

  • 프록시 객체 호출
  • Proxy.invoke() 시작
  • 트랜잭션 시작
  • 트랜잭션 전파 설정에 따른 내부 메소드 트랜잭션 처리
  • 트랜잭션 커밋
  • Proxy.invoke() 종료

정리하자면 Spring AOP 방식의 트랜잭션은 메소드 단위로 관리된다. 즉 메소드가 끝날 때까지 커밋 또는 커넥션 반환이 이루어지지 않는다. 트랜잭션 대상 메소드 내에서 발생하는 쿼리문은 동일한 커넥션을 사용한다. 따라서 처리 시간이 긴 메소드의 경우에는 트랜잭션 단위를 조정해서 DB Lock 지속시간이 길어지거나 커넥션 풀의 커넥션 개수가 모자라지 않도록 해야 한다.

(참고 - https://steady-coding.tistory.com/610)


마지막 정리에서 가장 중요한 부분으로 생각되는건 ‘트랜잭션이 메소드 단위로 관리된다’ 라는 점이다.

필자도 실제 맞닥뜨린 이슈의 로그에 db connection timeout 이 찍혀있길래

디버깅 해보니 하나의 메서드에서 여러개의 테이블을 업데이트하며 생기는 트랜잭션 오류였던 경우였다.

하나의 메소드가 끝날 때까지 커밋과 커넥션반환이 이루어지지 않으므로

‘한번에 하나씩’ 즉, ‘한 트랜잭션은 한 메서드씩’ 이라는 생각을 가지는 습관이 필요한 것 같다.

profile
Deepveloper

0개의 댓글