Wookim

spring boot Transaction 적용과 분할 본문

카테고리 없음

spring boot Transaction 적용과 분할

개발자인 경우 2021. 7. 14. 16:58

Transaction

데이터베이스 트랜잭션(Database Transaction)은

데이터베이스 관리 시스템의 상호작용의 단위이다.

이하 트랜잭션이라 칭함.

 

더보기

자세한 내용은 아래 글 참조

https://coding-factory.tistory.com/226


spring boot의 트랜잭션 적용전 코드

  1. 인터페이스
/** 인터페이스 */ 
public interface SomeService { 
    void someMethod1() throws Exception; 
    void someMethod2() throws Exception; 
    void someMethod3() throws Exception; 
}

 

  2. 서비스

@Service
public class SomeServiceImpl implements SomeService{
    @Override
    public void someMethod1() throws Exception{
        try{
            getSomeData(); // DB 조회
            insertSomeData(); // DB 삽입
            updateSomeData(); // DB 업데이트
        }catch (Exception e){
            updateFailLog(); // DB 업데이트
            throw new RuntimeException("someMethod1 fail.");
        }
    }

    private void getSomeData(){
        //....
    }

    private void insertSomeData(){
        //....
    }

    private void updateSomeData(){
        //....
    }

    private void updateFailLog(){
        //....
    }

    @Override
		...
}

상황

서비스 코드가 위와 같을 때

getSomeData() // DB 조회
insertSomeData() // DB 삽입
updateSomeData() // DB 업데이트

 

someMethod1 이 예외가 발생한다면

위 3가지 메소드들의 DB 작업들은 전부 롤백되어야 하는 상황이다.


spring boot의 트랜잭션 적용

    @Override
    @Transactional
    public void someMethod1() throws Exception{
        try{
            getSomeData(); // DB 조회
            insertSomeData(); // DB 삽입
            updateSomeData(); // DB 업데이트
        }catch (Exception e){
            updateFailLog(); // DB 업데이트
            throw new RuntimeException("someMethod1 fail.");
        }
    }

 

위 코드에서 수정사항은 @Transactional 애너테이션(어노테이션)이다.

해당 애너테이션은 someMethod1 이 실행 될 때,  스프링프레임워크가 트랜잭션을 생성하고

DB와의 작업 단위들을 하나로 묶어 실행한다.

 

getSomeData(), insertSomeData(), updateSomeData() 3가지 메소드들은 하나의 트랜잭션으로 묶여 있기 때문에

DB의 작업사항들이 commit 되기 위해선 전부 정상처리되어야 하며, 하나라도 실패(예외 발생) 했다면,

모든 작업들은 rollback 된다. (원자성)

더보기

getSomeData(), insertSomeData().. 등 메소드에서 예외가 발생하여 트랜잭션을 롤백해야 할 때.

기본적으로 RuntimeException 예외가 발생한다면 해당 트랜잭션은 전부 롤백된다. (기본)

만약 다른 예외 상황이 발생했을 때에만 롤백처리를 하고 싶다면, 이를 명시하는 옵션을 추가해야 한다.

 

참고

https://reiphiel.tistory.com/entry/understanding-of-spring-transaction-management-practice


눈썰미 테스트

위 코드를 읽어보면 이상한 점이 하나 있다.

catch 부분에 updateFailLog() 라는 메소드가 있다.

해당 메소드는 DB 업데이트를 실행한다.

 

만약 예외가 발생해, catch 블럭의 updateFailLog() 코드가 실행된 이후

RuntimeExceptionThrow하면 어떻게 될까?

 

해당 쿼리가 실행된후, 모든 트랜잭션 작업들은 롤백된다.

즉 쓸모없는 짓을 하고있는 거다.

 

이를 해결하는 방법은 무엇일까?


spring transaction 분리

가장 간단한 방법은 트랜잭션을 분리하는 것이라 생각한다. (표현이 맞는지 모르겠지만... 쉽게 생각하자면)

프로세스를 정리해보자.

 

프로세스

  1. (성공) someMethod1 호출 → 실행 → 트랜잭션 1 생성 → 작업 성공 → 커밋
  2. (실패) someMethod1 호출 → 실행 → 트랜잭션 1생성 → 예외 발생 → 트랜잭션 2 생성 → 트랜잭션 2 커밋 → 트랜잭션 1 롤백

위와 같이 트랜잭션을 분리하여 각각 관리하게 된다면 위와 같은 문제를 해결 할 수 있다.

(이 와 같은 해결책이 바람직하다고 생각하지 않지만, 필요하다면 사용해야 한다고 생각함)

 

 

  1. 인터페이스
/** 인터페이스 */
public interface SomeService {
    void someMethod1() throws Exception;
    void someMethod2() throws Exception;
    void someMethod3() throws Exception;
    void updateFailLog() throws Exception; // 내부 코드 공개
}
  1. 서비스
@Service
public class SomeServiceImpl implements SomeService{

    @Autowired
    SomeService self; // 인터페이스 객체를 DI 함 생성자 주입시 순환에러 발생 -> autowired로..

    @Override
    @Transactional
    public void someMethod1() throws Exception{
        try{
            getSomeData();
            insertSomeData();
            updateSomeData();
        }catch (Exception e){
            self.updateFailLog(); // DI한 빈 객체을 이용해 외부 메소드 사용
            throw new RuntimeException("someMethod1 fail.");
        }
    }

    private void getSomeData(){
        //....
    }

    private void insertSomeData(){
        //....
    }

    private void updateSomeData(){
        //....
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW,
            noRollbackFor = RuntimeException.class)
    public void updateFailLog(){
        // 내부 메소드를 공개처리하여 스프링이 프록시 코드를 작성하고 
        // 이를 사용하도록 수정해야함 ( self.updateFailLog() )
        //....
    }

    @Override
    public void someMethod2() throws Exception{

    }

    @Override
    public void someMethod3() throws Exception{

    }
}

위 코드를 보면 updateFailLog 메소드를 override 하고 @Transactional 애너테이션이 붙으며

여러가지 옵션이 들어간 것을 알 수 있다.

 

propagation 은 전파 속성이다.

noRollbackFor 은 특정 예외가 발생해도 롤백처리를 하지 말라는 설정이다.

https://bamdule.tistory.com/51

위 글에 정리가 잘되어 있다.

 

위와 같은 설정을 하면

 

someMethod1 호출 → 실행 → 트랜잭션 1생성

→ 예외 발생 → 트랜잭션 2 생성 → 트랜잭션 2 커밋 → 트랜잭션 1 롤백

 

와 같이 처리가 된다.

 

스프링이 @Transactional 애너테이션을 어떻게 처리하는지 궁금하다면

다음 글을 읽어보자! 정말 좋은 글이다.

https://mommoo.tistory.com/92

 


테스트 결과

예외가 발생하여 updateFailLog() 메소드가 실행되면 

해당 쿼리는 commit 되어 유지되는 것을 확인 했다.

 

마무리..

평소 트랜잭션 기능을 적용만 하고 크게 신경쓰지 않았다.

 

그러던 중 트랜잭션을 분리하여 각각으로 관리해야 하는 일이 생겼다.

 

방법을 찾던 중 좋은 글들을 많이 읽게 되었다.

 

그 글들의 내용을 그대로 복붙 하기보다, 내가 처리한 사항을 예제로 작성하여 보았다.

 

@Async와 마찬가지로 @Transactional 도 

스프링이 눈에 보이지 않게 관리를 하고 있다는 것을 알게되었다.

 

둘다 프록시 혹은 바이트 코드로 내부적으로 처리되며, 

적용한 메소드들은 클래스 내부 함수로 사용할 수 없다는 이유도 몸에 와닿게 되었다.


Reference

https://coding-factory.tistory.com/226

https://reiphiel.tistory.com/entry/understanding-of-spring-transaction-management-practice

https://bamdule.tistory.com/51

https://mommoo.tistory.com/92

Comments