본 프로젝트 자료는 김영한님의 스프링 DB 1편 - 데이터 접근 핵심 원리을 참고 제작됐음을 알립니다.
트랜잭션 - 적용1
실제 애플리케이션에서 DB 트랜잭션을 사용해서 계좌이체 같이 원자성이 중요한 비즈니스 로직을 어떻게 구현하는지 알아보자.
먼저 트랜잭션 없이 단순하게 계좌이체 비즈니스 로직만 구현해보자.
MemberServiceV1 - 생성
MemberServiceV1Test - 테스트 생성
정리
이체중 예외가 발생하게 되면 memberA 의 금액은 10000원 8000원으로 2000원 감소한다.
그런데 memberEx 의 돈은 그대로 10000원으로 남아있다. 결과적으로 memberA 의 돈만 2000원 감소한 것이다!
트랜잭션 - 적용2
- 트랜잭션은 비즈니스 로직이 있는 서비스 계층에서 시작해야 한다.
- 트랜잭션을 시작하려면 커넥션이 필요하다. 결국 서비스 계층에서 커넥션을 만들고, 트랜잭션 커밋 이후에 커넥션을 종료해야 한다.
- 애플리케이션에서 DB 트랜잭션을 사용하려면 트랜잭션을 사용하는 동안 같은 커넥션을 유지해야한다. 그래야 같은 세션을 사용할 수 있다.
애플리케이션에서 같은 커넥션을 유지하려면 어떻게 해야할까? 가장 단순한 방법은 커넥션을 파라미터로 전달해서 같은 커넥션이 사용되도록 유지하는 것이다.
먼저 리포지토리가 파라미터를 통해 같은 커넥션을 유지할 수 있도록 파라미터를 추가하자.
MemberRepositoryV2 - 추가
주의 - 코드에서 다음 부분을 주의해서 보자!
- 커넥션 유지가 필요한 두 메서드는 파라미터로 넘어온 커넥션을 사용해야 한다. 따라서 con = getConnection() 코드가 있으면 안된다.
- 커넥션 유지가 필요한 두 메서드는 리포지토리에서 커넥션을 닫으면 안된다. 커넥션을 전달 받은 리포지토리 뿐만 아니라 이후에도 커넥션을 계속 이어서 사용하기 때문이다. 이후 서비스 로직이 끝날 때 트랜잭션을 종료하고 닫아야 한다.
MemberServiceV2 - 추가
이제 테스트를 돌려보자.
MemberServiceV2Test - 테스트 추가
이체중 예외 발생 - accountTransferEx()
- 다음 데이터를 저장해서 테스트를 준비한다.
- memberA 10000원
- memberEx 10000원
- 계좌이체 로직을 실행한다.
- memberService.accountTransfer() 를 실행한다.
- 커넥션을 생성하고 트랜잭션을 시작한다.
- memberA memberEx 로 2000원 계좌이체 한다.
- memberA 의 금액이 2000원 감소한다.
- memberEx 회원의 ID는 ex 이므로 중간에 예외가 발생한다.
- 예외가 발생했으므로 트랜잭션을 롤백한다.
- 계좌이체는 실패했다. 롤백을 수행해서 memberA 의 돈이 기존 10000원으로 복구되었다.
- memberA 10000원 - 트랜잭션 롤백으로 복구된다.
- memberB 10000원 - 중간에 실패로 로직이 수행되지 않았다. 따라서 그대로 10000원으로 남아있게 된다.
트랜잭션 덕분에 계좌이체가 실패할 때 롤백을 수행해서 모든 데이터를 정상적으로 초기화 할 수 있게 되었다. 결과적으로 계좌이체를 수행하기 직전으로 돌아가게 된다.