Spring/Spring 기본 지식

spring transaction roll back 처리

레알윙 2021. 6. 20. 15:15
반응형

회사 프로젝트를 확인하다가 try catch와 transation에 대해서 궁금한한게 생겼다.

 

궁금한 내용들은

각각의 예외처리가 된 메소드에서 에러가 발생이될 때 서로의 관계가 어떻게 될까? 였다. 이에 따른 현상을 이해하기 위해서 

우선적으로 Excatpion과 Error의 기본적인 개념을 알면서 공부하자

아래의 링크를 타고가게 된다면 기본적인 개념에 대해서 적었지만 이번 글에서는 좀 더 나아가서 공부할 예정이다.

2020.07.19 - [Java & 배경지식/기본상식] - 자바 개발자가 알아야하는 25가지 상식!

 

아래에 관련된 코드는 여기 확인하면 된다.

 

Exception과 Error

Exception과 Error는 한 마디로 개발자가 대응을 할 수 있냐 없냐로 구분할 수 있다.

쉽게 말하면 Erorr는 OutOfMemery처럼 메모리가 부족해서 발생되는 것이기 때문에 개발자가 대응을 할 수가 없으며

Exception은 NullPointException처럼 개발자가 대응을 할 수 있는 있는 것을 뜻한다.

 

Exception의 종류

  Check Exception nonChecked Exception
정의  Exception의 상속받는 하위 클래스 중 Runtime Exception을 제외한 모든 Exception Runtime Exception 하위 Exception
발생이유 주로 외부의 영향으로 발생할 수 있는 것들로서, 프로그램의 사용자들의 동작에 의해서 발생하는 경우가 많다.
예를 들면, 존재하지 않는 파일을 처리하려는 경우(FileNotFoundException), 실수로 클래스의 이름을 잘못 적은 경우(ClassNotFoundException), 입력한 데이터의 형식이 잘못된 경우(DataFormatException)에 발생
주로 프로그래머의 실수에 의해서 발생될 수 있는 예외들로서 자바의 프로그래밍 요소들과 관계가 깊다.
예를 들면, 배열의 범위를 벗어난 경우(IndexOutOfBoundsException), 값이 null인 참조 변수의 멤버를 호출한 경우(NullPointerException), 클래스 간의 형 변환을 잘못한 경우(ClassCastException), 정수를 0으로 나누려 하는 경우(ArithmeticException)에 발생
처리여부 명시적인 예외 처리를 해주어야 한다. 명시적인 처리를 강제하지는 않는다.
혹인시점 Compile time
: 이미 컴파일 시점에 에러를 나타내기 때문에 try~catch~finally 또는 throws 구문을 통해서 처리할 수 있게 IDE에서 알려준다.
Runtime
: 이미 컴파일이 끝나고, 애플리케이션이 서비스가 런타임일 때 발생하기 때문에 try~catch 또는 throws 구문을 사용해서 로직상에서 방어 코드를 만들어야 한다.
예외발생시 트랜잭션 처리 non-rollback rollback

 

Exception 처리 방법

  • try catch finally - 직접 처리 방식
  • throws - 간접 처리 방식

직접 처리 방식이 무엇인지 간접처리 방식이 무엇인지 글로 표현하자면 직접 내가 Exception처리를 하는가 아니면 Exception처리를 남에게 미루는가 이렇게 볼 수 있다. 이를 코드로 표현하자면 아래와 같다.

 

try catch finally - 직접 처리 방식

public void directException() {
    try {
        System.out.println("Exception 이 발생하지 않는다면 실행하는 공간!.");
        throw new Exception("직접 처리 방식");
    } catch (Exception e) {
        System.out.println("Exception 발생하면 실행 : " + e.getMessage());
    } finally {
        System.out.println("무조건 실행");
    }
}

실행 결과

 

throws - 간첩 처리 방식

public void inDirectException() throws Exception {
    throw new Exception("간접 처리 방식 !");
}

public void callInDirectException() {
    try {
        System.out.println("여기는 call");
        inDirectException();
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
}

 

실행결과

 

 

 

 

Spring Transaction

이제 중간에 에러가 발생이 되었을 때 트랜잭션이 어떻게 처리하는가에 대해서 알아 보자.

 

상황 1.  try ~ catch문안에서 Exception이 발생된 경우

아래의 코드와 결과를 보게 된다면 Exception이 발생한 경우 Member1의 대한 정보만 저장이되고, Member2의 정보는 저장이 안된다.

 

/**
 * 직접 처리 방식내에서 오류가 발생 할 때 Transaction 처리 여부 확인
 * @throws Exception
 */
@Transactional
public void simulation1() throws Exception {
    Member member1 = Member.builder()
            .name("Member1")
            .nickname("닉테임1")
            .email("test1@test.com")
            .memberMakeSample();
    Member member2 = Member.builder()
            .name("Member2")
            .nickname("닉테임2")
            .email("test2@test.com")
            .memberMakeSample();

    memberRepository.save(member1);

    try {
        sampleRepository.failMethod();
        memberRepository.save(member2);
    } catch (Exception e) {
        System.out.println("에러 발생!1!!!");
    }

    /*
    Member가 저장이 되어 있다면 해당 ID들은 저장이 되어있을 것
     */
    System.out.println("member1 id : " + member1.getMemberId());
    System.out.println("member2 id : " + member2.getMemberId());
}

 

상황 2.  간접 처리시 Exception이 발생된 경우

아래와 같이 모두 저장이 되지 않으며, Transaction 오류가 발생함을 알 수 있다.

/**
 * 간접 처리 방식내에서 오류가 발생 할 때 Transaction 처리 여부 확인
 * @throws Exception
 */
@Transactional
public void simulation2() throws Exception {
    Member member1 = Member.builder()
            .name("Member1")
            .nickname("닉테임1")
            .email("test1@test.com")
            .memberMakeSample();

    Member member2 = Member.builder()
            .name("Member2")
            .nickname("닉테임2")
            .email("test2@test.com")
            .memberMakeSample();

    memberRepository.save(member1);
    sampleRepository.failMethod();
    memberRepository.save(member2);

    /*
    Member가 저장이 되어 있다면 해당 ID들은 저장이 되어있을 것
     */
    System.out.println("member1 id : " + member1.getMemberId());
    System.out.println("member2 id : " + member2.getMemberId());
}

 

결과

 

그외.  try ~ catch에서 rollbackFor을 다르게 준다면

rollbackFor는 해당 Exception이 작동이 될 때 rollback을 하는 옵션이다.

1. 정상적으로 작동이 안될 때

Exception이 발생이 될때 rollback 한다 하였으나 rollack이 안되는 경우

/**
 * @Transactional(rollbackFor = Exception.class) 옵션 먹힘
 * @throws Exception
 */
@Transactional(rollbackFor = Exception.class)
public void simulation3() throws Exception {
    Member member1 = Member.builder()
            .name("Member1")
            .nickname("닉테임1")
            .email("test1@test.com")
            .memberMakeSample();
    Member member2 = Member.builder()
            .name("Member2")
            .nickname("닉테임2")
            .email("test2@test.com")
            .memberMakeSample();

    memberRepository.save(member1);

    try {
        sampleRepository.failMethod();
        memberRepository.save(member2);
    } catch (Exception e) {
        System.out.println("에러 발생!1!!!");
    }

    /*
    Member가 저장이 되어 있다면 해당 ID들은 저장이 되어있을 것
     */
    System.out.println("member1 id : " + member1.getMemberId());
    System.out.println("member2 id : " + member2.getMemberId());
}

 

2. 정상적으로 작동이 될 때

try~catch문을 사용했을 때 해당 옵션에 대해 제대로 작동시키기 하기 위해서는 catch문에라도 Exception을 발동 시켜야 한다.

/**
     * @Transactional(rollbackFor = Exception.class) 옵션 안먹힘
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class)
    public void simulation4() throws Exception {
        Member member1 = Member.builder()
                .name("Member1")
                .nickname("닉테임1")
                .email("test1@test.com")
                .memberMakeSample();
        Member member2 = Member.builder()
                .name("Member2")
                .nickname("닉테임2")
                .email("test2@test.com")
                .memberMakeSample();

        memberRepository.save(member1);

        try {
            sampleRepository.failMethod();
            memberRepository.save(member2);
        } catch (Exception e) {
            throw new Exception();
        }

        /*
        Member가 저장이 되어 있다면 해당 ID들은 저장이 되어있을 것
         */
        System.out.println("member1 id : " + member1.getMemberId());
        System.out.println("member2 id : " + member2.getMemberId());
    }

결과

 

 

상황 3. Transaction이 처리된 메소드들을 호출할때 에러발생시 어디서 에러처리를 하는가?

 1. Transaction이 걸려있는 메소드가 서로를 호출할 때 어디서 잡는가?

아래의 코들르 보게 된다면 step1, step2, step3 메소드가 존재하며 step3에 에러가 발생된 것이 확인이 된다. 해당코드를 디버깅한다면 

에러발생지점인 step3에서 에러처리를 하는 것을 알 수 있다. 그리고  rolback을 진행한다.

    /**
     * @throws Exception
     * @Transactional(rollbackFor = Exception.class) 옵션 안먹힘
     */
    @Transactional(rollbackFor = Exception.class)
    public void simulation5() throws Exception {
        step1Transaction();
        System.out.println("simulation5");
    }


    @Transactional(rollbackFor = Exception.class)
    public void step1Transaction() throws Exception {
        Member member = Member.builder()
                .name("Member1")
                .nickname("step1")
                .email("test1@test.com")
                .memberMakeSample();


        memberRepository.save(member);
        step2Transaction(member);
        System.out.println("step1Transaction");
    }

    @Transactional(rollbackFor = Exception.class)
    public void step2Transaction(Member member) throws Exception {
        member.setNickname("step2");
        memberRepository.save(member);
        step3Transaction(member);
        System.out.println("step2Transaction");
    }

    @Transactional(rollbackFor = Exception.class)
    public void step3Transaction(Member member) throws Exception {
        member.setNickname("step3");
        memberRepository.save(member);
        System.out.println("step3Transaction");
        throw new Exception();
    }

 

 

 

 

 

 

참고

https://interconnection.tistory.com/122

반응형