본문 바로가기
카테고리 없음

[Spring] Transaction 1

by dal_been 2023. 11. 19.
728x90

나는 단순히 트랜젝션을 롤백하기 위한 개념이라고 알고 있다. 물론 이말도 맞지만 뭐랄까...너무 대충알고 있는 느낌이랄까..??

그래서 6달전??에 김영한님 스프링 db와 관련된 트랜젝션 강의와 내가 찾아본것을 기반으로 정리해두려고한다.

사실 가장 정리하는 큰 이유는 msa에서 과연 어떻게 트랜젝션을 관리할까 궁금증에 시작된 정리이다.

 

 

 

트랜젝션이 뭐냐고??

 

트랜젝션을 예를 들자면 가장 흔한 계좌이체라고 할 수 있다.

만약 내가 철민이에게 5000월을 보낸다. 이상황에서 일어나는 것을 간소화하자면

- 나의 잔고 -5000원

- 철민 잔고 +5000원이다

 

근데 여기서 나의 잔고감소는 성공했는데 예상치 못한 에러로 철민잔고 증가에 실패했다면..??

아주아주 대형사고다..

 

이때 데이터베이스가 제공하는 기능이 트랜젝션기능이다.

나의잔고, 철민 잔고 둘다 성공하거나 둘다 실패하거나 하게 해준다.

여기서 둘다 성공하여 데이터베이스에 반영되는 것을 commit

둘중 하나라도 실패하여 둘다 거래 이전으로 되돌리는 것을 rollback

 

 

트랜젝션 ACID

 

트랜젝션하면 ACID라는 개념이 같이 나온다.

 

  • 원자성 : 트랜젝션 내에서 실행한 작업들은 마치 하나의 작업인 것처럼 모두 성공하거나 모두 실패
  • 일관성 : 모든 트랜젝션은 일관성있는 데이터 상태를 유지해야함 -> 데이터베이스에서 정한 무결성 제약조건을 항상 만족해야함
  • 격리성 : 동시에 실행되는 트랜젝션들이 서로 영향을 미치지않도록 해야함 -> 동시에 같은 데이터를 수정하지 못하도록(트랜젝션 격리수준을 선택할 수 있음)
  • 지속성 : 트랜젝션을 성공적으로 끝내면 그 결과가 항상 기록되어야한다. 중간에 시스템 문제가 발생해도 데이터베이스 로드등을 사용해서 성공한 트랜젝션 내용을 복구해야함

 

트랜젝션 격리수준

  • Read uncommited
  • Read commited
  • Repeatable Read
  • Serializable(직렬화)

 

데이터베이스 연결 구조 와 DB 세션

 

 

트랜젝션에 대해 더 자세하게 이해하기 위해 데이터베이스 연결구조를 보자

 

- 사용자는 웹애플리케이션 서버(was)나 db접근 툴 같은 클라이언트를 사용해서 데이터 베이스 서버에 접근.

 -> 클라이언트는 데이터베이스에 서버연결을 요청하고 커넥션을 맺음

 -> 이때 데이터베이스는 내부에 세션이라는 것을 만들어서 해당 커넥션을 통한 모든 요청은 이 세션을 통해 실행

 

- 세션은 트랜젝션을 시작하고, 커밋또는 롤백을 통해 트랜젝션을 종료함. 이후에 새로운 트랜젝션을 다시 사작가능

- 커넥션 풀이 10개의 커넥션을 생성하면, 세션도 10개가 만들어짐

 

 

DB 락 개념

 

 

세션 1이 트랜젝션을 시작하고 수정하는 동안 커밋을 하지 않았는데 세션 2가 동시에 같은 데이터를 수정하게 되면 트랜젝션의 원자성이 깨지게된다(둘다 성공하거나 둘다 실패하거나)

여기에다가 만약 세션 1이 중간에 롤백을 하게되면 세션2는 잘못된 데이터를 수정하는 문제가 발생한다.

 

이런 문제를 방지하기 위해 세션이 트랜젝션을 시작하고 데이터를 수정하는 동안에 커밋이나 롤백전까지 다른 세션에서 해당 데이터를 수정할 수 없게 막아야한다.

 

그게 바로 락이다

 

위의 사진을 보면 세션1이 500원을 더하고자 하고 세션2는 1000을 더하고자한다.

이때 세션1이 트랜젝션을 먼저 시작하면 해당 로우에 대한 락을 먼저 획득하여 세션2가 세션1이 수정하는 동안 대기하게 한다.

다시 말해 한 세션이 락을 획득한 동안에는 데이터 변경에 대한 시도를 하지 못한다. 락을 획득한 세션이 락을 반납할때까지 기다려야한다

 

다만 여기서 무한정을 락을 기다리게 해서는 안된다. 락에 대한 대기시간 설정을 통해 조절한다.

SET LOCK_TIMEOUT <milliseconds>

 

 

 

여기서 질문 조회에서도 락을 필요로 할까??

뭐 상황에 따라 다르겠지만 데이터를 수정하지 않는 이상 보통 단순조회는 락이 필요없다.

다만 조회에서도 락을 획득하고 싶다면 "select for update"구문을 사용하면 된다.

 

 

간단 트랜젝션 적용

 

 

트랜젝션은 비즈니스 로직이 있는 서비스 계층에서 시작해야한다. 비즈니스 로직이 잘못되면 해당 비즈니스 로직으로 인해 문제가 되는 부분을 함께 롤백해야하기때문.

다만 트랜젝션을 시작하려면 커넥션이 필요하다. 결국 서비스 계층에서 커넥션을 만들고, 트랜젝션 커밋이후에 커넥션을 종료해야한다.

애플리케이션에서 db트랜젝션을 사용하려면 트랜젝션을 사용하는 동안 같은 커넥션을 유지해야한다. 그래야 같은 세션을 사용할 수 있다

 

 

  @Slf4j
  @RequiredArgsConstructor
  public class MemberServiceV2 {
      private final DataSource dataSource;
      private final MemberRepositoryV2 memberRepository;
      public void accountTransfer(String fromId, String toId, int money) throws SQLException {
          Connection con = dataSource.getConnection();
          try {
                con.setAutoCommit(false);//트랜잭션 시작 //비즈니스 로직
                bizLogic(con, fromId, toId, money); 
                con.commit();//성공시 커밋
             } catch (Exception e) { 
             	 con.rollback();//실패시 롤백
                 throw new IllegalStateException(e);
             } finally {
             	 //커넥션을 모두 사용하고 안전하게 종료(풀에 반납)
                 release(con);
             }
	}
    
     private void bizLogic(Connection con, String fromId, String toId, int money) throws SQLException {
         Member fromMember = memberRepository.findById(con, fromId);
         Member toMember = memberRepository.findById(con, toId);
         memberRepository.update(con, fromId, fromMember.getMoney() - money);
         validation(toMember);
         memberRepository.update(con, toId, toMember.getMoney() + money);
	}
  }

 

그냥 간단하게 돈을 이체하는 코드다.

여기서 con= getConnection(); 을 통해서 하나의 세션에서 서비스 로직이 이루어지게 하였다.

 

 

 

 

 

 

해당 내용은 인프런_김영한님의 스프링 DB 1편을 참고하였습니다.