트랜잭션 -적용하기

JIWOO YUN·2024년 4월 12일
0

SpringDB

목록 보기
7/11
post-custom-banner

트랜잭션 - 적용하기

애플리케이션에서 DB 트랜잭션을 적용하기

적용해보기 전 비교를 위해 트랜잭션을 적용하지 않고 진행

트랜잭션이 적용되지 않은 MemberServiceV1

@RequiredArgsConstructor
public class MemberServiceV1 {

    private final MemberRepositoryV1 memberRepository;

    public void accountTransfer(String fromId, String toId, int money) throws SQLException {
        Member fromMember = memberRepository.findById(fromId);
        Member toMember = memberRepository.findById(toId);

        memberRepository.update(fromId, fromMember.getMoney() - money);
        validation(toMember);
        memberRepository.update(toId, toMember.getMoney() + money);
    }

    private void validation(Member toMember) {
        if (toMember.getMemberId().equals("ex")) {
            throw new IllegalStateException("이체중 예외 발생");
        }
    }
}

이체중 예외발생 테스트 코드

@Test
@DisplayName("이체중 예외 발생")
void accountTransferEx() throws SQLException {

    Member memberA = new Member(MEMBER_A, 10000);
    Member memberEx = new Member(MEMBER_EX, 10000);
    memberRepository.save(memberA);
    memberRepository.save(memberEx);

    assertThatThrownBy(() -> memberService.accountTransfer(memberA.getMemberId(), memberEx.getMemberId(), 2000))
            .isInstanceOf(IllegalStateException.class);

    Member findMemberA = memberRepository.findById(memberA.getMemberId());
    Member findMemberEx = memberRepository.findById(memberEx.getMemberId());

    assertThat(findMemberA.getMoney()).isEqualTo(8000);
    assertThat(findMemberEx.getMoney()).isEqualTo(10000);
}
  • 계속 공부하던 문제를 테스트를 통해서 실행해서 확인 해본 결과
  • memberA의 경우 8000원을 들고 있게 되고 memberEx의 경우 10000원을 가지게 되는 문제가 발생한다.
    • 회원의 id가 ex이므로 중간에 예외가 발생
    • 하지만 트랜잭션 내부에서 발생한 것이 아니기때문에 그냥 커밋되는 결과가 발생해서 membeA의 돈은 줄어들게되고 memberEx는 돈이 그대로가 되버린다.

DB 트랜잭션을 사용해서 앞서 발생한 문제점 해결 하자.

  • 트랜잭션은 비즈니스 로직이 있는 서비스 계층에서 시작해야함 -> 비즈니스 로직이 잘못되면 해당 비즈니스 로직으로 문제가 되는 부분을 함께 롤백해야하기 때문
    • 트랜잭션을 시작하려면 커넥션이 필요하기 때문에 서비스 계층에서 커넥션을 만들고, 트랜잭션 커밋 이후에 커넥션 종료가 수순이다.
  • 애플리케이션에서 DB 트랜잭션을 사용하려면 트랜잭션을 사용하는 동안 같은 커넥션을 유지해야한다. => 이렇게 해야 같은 세션을 사용할 수 있기 때문에.

이전 repo는 남겨두기위해 MemberRespositoryV2 추가

  • 기존 v1버전에서 findById와 update 부분에 커넥션 유지를 위해서 넣어준다.
    public Member findByid(Connection con, String memberId) throws SQLException {
        String sql = "select * from member where member_id = ?";

        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, memberId);

            rs = pstmt.executeQuery();

            if (rs.next()) {
                Member member = new Member();
                member.setMemberId(rs.getString("member_id"));
                member.setMoney(rs.getInt("money"));
                return member;
            } else {
                throw new NoSuchElementException("member not found memberId=" + memberId);
            }

        } catch (SQLException e) {
            log.info("db error ", e);
            throw e;
        } finally {
            JdbcUtils.closeResultSet(rs);
            JdbcUtils.closeStatement(pstmt);
        }
    }



public void update(Connection con, String memberId, int money) throws SQLException {
    String sql = "update member set money=? where member_id=?";

    PreparedStatement pstmt = null;

    try {
        pstmt = con.prepareStatement(sql);
        pstmt.setInt(1,money);
        pstmt.setString(2,memberId);
        pstmt.executeUpdate();

    }catch (SQLException e){
        log.info("db error",e);
        throw e;
    }finally {
        JdbcUtils.closeStatement(pstmt);
    }
}

트랜잭션 연동 로직 추가

  • 기존 MemberServiceV1을 남겨두고 V2를 만들어서 수정 진행.
@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);
    }

    private void validation(Member toMember) {
        if (toMember.getMemberId().equals("ex")) {
            throw new IllegalStateException("이체중 예외 발생");
        }
    }

    private void release(Connection con){
        if(con != null){
            try{
                con.setAutoCommit(true);
                con.close();
            }catch (Exception e){
                log.info("error",e);
            }
        }
    }
}

accountTransfer 함수 추가된 것들

  • 커넥션 유지를 위해서 커넥션도 같이 넣어준다.
  • 트랜잭션 시작을 위해서 자동 커밋을 꺼줘야한다.
  • 비즈니스 로직이 끝나고 commit() 호출 해주기 -> 자동커밋이 꺼졌기 때문에 필수 요소
    • 만약 문제가 생기면 rollback() 호출

release()

  • finally에서 커넥션을 모두 사용시 안전하게 종료할 때 수동 커밋으로 설정했던걸 풀로 돌려주기 전에 기본 값인 자동 커밋 모드로 변경시켜 주기 위한 함수
    • 안전하게 쓰기 위해서 사용

비즈니스 로직 분리 이유?

  • 트랜잭션을 관리하는 로직과 실제 비즈니스 로직을 구분하기 위해서 사용되었다.
profile
열심히하자
post-custom-banner

0개의 댓글