본문 바로가기
Programming

java Exception 전파

by BTC_동동 2024. 2. 8.

베하!

안녕하세요 여러분! 일단고 팀입니다.

 

 

 

오늘은 개발을 진행하다가 겪은 java의 기초이자 중요한 부분인 Exception 관련한 내용입니다.

저는 트랜잭션이 작동하기 위해 테스트하는 과정에서 Exception에 대해 설명 드린적이 있었고 그 과정에서 저는 Exception이 특정 함수를 호출한 함수에서만(try-catch를 처리 유무와 상관없이) 해당 예외가 전파되는 줄 알았습니다…

하지만 최근 특정 로직에 대해 커스텀 예외를 던지고 그 예외에 대한 메시지를 API 클라이언트까지 전달하기 위해서 작업을 하던 중 예외 전파 범위에 대해 알게 되어 공유드리고자 합니다.

 

Exception 전파

A method가 있고 B method가 있고 C method가 있을 때 C에서 Exception이 발생하면 해당 예외는 어디까지 전달될까요?

해당 문제는 상황에 따라 다르게 됩니다.

다르게 되는 상황은 어디 method에서 try - catch로 예외를 처리하냐에 따라 다르게 됩니다.

예외 전파 테스트

다음과 같이 응답 model을 정의 했습니다.

package com.bespin.sr.manage.model.delegate;

import com.bespin.common.model.ErrorModel;
import com.bespin.common.model.ResponseSpecification;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class CommonResultModel {

    public static final String STATUS_OK = "OK";
    public static final String STATUS_FAIL = "fail";

    @JsonProperty("status")
    private String status = STATUS_OK;

    @JsonProperty(value = "Data", required = false)
    private Object data = null;

    @JsonProperty(value = "error")
    private ErrorModel error = null;

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public void setError(ResponseSpecification code) {
        if(error == null) {
            error = new ErrorModel();
        }

        status = STATUS_FAIL;
        error.setCode(code.getCode());
        error.setMessage(code.getMessage());
    }

    public void setError(int code, String message) {
        if(error == null) {
            error = new ErrorModel();
        }

        status = STATUS_FAIL;
        error.setCode(code);
        error.setMessage(message);
    }

    public ErrorModel getError() {
        return error;
    }
}
public class ExceptionTest {

    @Test
    public void tempMain(){
        CommonResultModel resultModel = new CommonResultModel();
        try {
            C();
            resultModel.setData("succ");
            System.out.println("상태 값: "+resultModel.getStatus());
        } catch (RuntimeException e){
            resultModel.setError(500,e.getMessage());
            System.out.println("상태 값: "+resultModel.getStatus());
            System.out.println("상태 메시지:  "+resultModel.getError().getMessage());
        }
    }

    public void A(){
        B();
    }

    public void B(){
        C();
    }

    public void C() throws RuntimeException {
        String errMsg = "C method Exception";
        throw new RuntimeException(errMsg);
    }
}

다음과 같이 spring에서 Test를 통해 예외 전파를 테스트 해보겠습니다.

위 코드에서 tempMain이 예외를 발생하는 C를 호출하고 있습니다. tempMain은 예외를 처리하는 try-catch문이 존재합니다.

여러분들도 알고 계시다시피 tempMain은 C를 try 구문 안에서 호출했고 예외가 발생했기 때문에 콘솔에서 결과 값은 아래와 같이 됩니다.

상태 값: fail
상태 메시지:  C method Exception

이 C 함수를 A, B를 거쳐서 예외를 발생한다면 tempMain까지 도달하는지 확인해보겠습니다.

    @Test
    public void tempMain(){
        CommonResultModel resultModel = new CommonResultModel();
        try {
            A();
            resultModel.setData("succ");
            System.out.println("상태 값: "+resultModel.getStatus());
        } catch (RuntimeException e){
            resultModel.setError(500,e.getMessage());
            System.out.println("상태 값: "+resultModel.getStatus());
            System.out.println("상태 메시지:  "+resultModel.getError().getMessage());
        }
    }

tempMain은 A를 호출하고 A는 B, B는 C를 호출합니다. 마찬가지로 C는 예외를 던지게 됩니다.

결과 값은

상태 값: fail
상태 메시지:  C method Exception

즉 tempMain까지 예외를 전달함을 알 수 있습니다.

중간에 예외 처리

만약 예외를 던지는 C를 B에서 예외처리 한다면 어떻게 될까요?

package com.bespin;

import com.bespin.sr.manage.model.delegate.CommonResultModel;
import org.junit.Test;

public class ExceptionTest {

    @Test
    public void tempMain(){
        CommonResultModel resultModel = new CommonResultModel();
        try {
            A();
            resultModel.setData("succ");
            System.out.println("TempMain 결과 상태 값: "+resultModel.getStatus());
        } catch (RuntimeException e){
            resultModel.setError(500,e.getMessage());
            System.out.println("TempMain 상태 값: "+resultModel.getStatus());
            System.out.println("TempMain 상태 메시지:  "+resultModel.getError().getMessage());
        }
    }

    public void A(){
        B();
    }

    public void B(){
        CommonResultModel resultModel = new CommonResultModel();
        try {
            C();
            resultModel.setData("succ");
            System.out.println("B 상태 값: "+resultModel.getStatus());
        } catch (RuntimeException e){
            resultModel.setError(500,e.getMessage());
            System.out.println("B 상태 값: "+resultModel.getStatus());
            System.out.println("B 상태 메시지:  "+resultModel.getError().getMessage());
        }
    }

    public void C() throws RuntimeException {
        String errMsg = "C method Exception";
        throw new RuntimeException(errMsg);
    }
}

전체 소스는 위와 같습니다.

결과는

B 상태 값: fail
B 상태 메시지:  C method Exception
TempMain 결과 상태 값: OK

tempMain에서는 예외를 받지 않았습니다. 즉 B에서 처리되었기 때문에 B를 호출하는 A, 그리고 A를 호출하는 tempMain까지 예외가 전파되지 않았음을 알 수 있습니다.

착각한 이유

먼저 저는 트랜잭션 테스트를 위해 controller-service-repository 구조에서 의도적으로 SQL 문법을 잘못되게 하여 SQLException을 만들어 service로 받아 트랜잭션을 테스트 하려고 했습니다.

하지만 service에서 예외가 발생해도 트랜잭션이 작동하지 않았습니다. 그렇게 dao에서 예외를 던져서 service에서 받는지 확인해보니 원하는 대로 service에서 트랜잭션이 작동했습니다.

이때 저는 예외를 던질 만한 함수를 호출하는 함수까지만 예외를 받는 줄 알았습니다.

하지만 위 테스트와 코드를 뜯어보며 착각이라는 것을 알게 되었습니다.

먼저 저는 mybatis를 사용했고 그 과정에서 많은 라이브러리를 사용했습니다. 단순히 제가 만들어낸 코드에는 전혀 볼 수 없었고 분석하려 들지도 않았지만 특정 예외에 대해서 라이브러리가 이미 try-catch로 작업을 진행한 것을 알게 되었고 그렇기 때문에 SQLException이 dao까지, service 까지 전파되지 않았음을 이해했습니다.

오늘 내용은 여기까지 입니다. 아주 기초적이고 너무 안일한 마인드로 라이브러리를 사용하다보니 제가 가지고 있는 확신에 금이 갔고 당황했습니다. 개발자는 단순히 제 코드 뿐 아니라 사용하려는 라이브러리의 구조까지 파악을 해야만 정확한 핵심 지식을 습득하고 사용할 수 있다고 느꼈습니다.

 

 

 

 

감사합니다. 여러분 모두 새해 복 많이 받으시고 풍요로운 명절되시길 바랍니다.

'Programming' 카테고리의 다른 글

정규표현식 플래그 활용  (0) 2024.02.23
정규표현식 활용  (1) 2024.01.21
[Mybatis] For input string 에러  (1) 2024.01.17
HTTP 상태 코드 정리  (2) 2024.01.13
Spring Boot Model validation 관련 애노테이션  (0) 2024.01.12

댓글