Transaction을 고려한 CheckedException 예외 처리

2023. 11. 12. 17:27프로젝트/[축팅]축제 소개팅 어플리케이션 - 카카오 테크 캠퍼스 1기

728x90

배경지식

 

CheckedException은 맨 아래에서 설명하고 있습니다.

 

 

문제 상황

 

현재 파일 시스템에 파일을 저장하고 있다. 아래 코드의 문제점이 무엇일까?

public List<UploadFile> storeFiles(List<MultipartFile> multipartFiles) throws IOException {
    List<UploadFile> storeFileResult = new ArrayList<>();
    for (MultipartFile multipartFile : multipartFiles) {
        storeFileResult.add(storeFile(multipartFile));
    }
    return storeFileResult;
}

@Transactional
public List<Certificate> saveCertifications(List<MultipartFile> certificates) throws IOException {
    List<UploadFile> uploadFiles = fileUtils.storeFiles(certificates);
    List<Certificate> certificateList = CertificateMapper.to(uploadFiles);
    certificateRepository.saveAll(certificateList);
    return certificateList;
}

 

 

아래 코드는 IOException이 발생하였을 때 @Transactional의 기본 동작에 따라 Rollback이 되지 않는다.

 

왜? Rollback이 되지 않는거야??

 

트랜잭션 내부

@Override
public boolean rollbackOn(Throwable ex) {
	return (ex instanceof RuntimeException || ex instanceof Error);
}

 

@Transactional 내부에는 rollbackOn이라는 메서드가 있는 데 이 메서드는 발생한 예외가 Error인지 RunTimeException인지를 가려내고 있다.

메서드에서 true가 반환되어야지 Rollback이 된다.

 

즉, 트랜잭션의 기본 동작은 UnCheckedException이 발생한 경우만 Rollback이 되는 것.

따라서, 위 코드는 IOException이 발생하더라도 Rollback이 되지 않는다.

 

해결?

 

처음에는 아래와 같은 예외처리 방식을 생각해봤다.

 

public List<UploadFile> storeFiles(List<MultipartFile> multipartFiles){
    List<UploadFile> storeFileResult = new ArrayList<>();
    for (MultipartFile multipartFile : multipartFiles) {
        try {
            storeFileResult.add(storeFile(multipartFile));
        } catch (IOException e) {
            throw new FileSaveFailedException(MessageCode.FILE_SAVE_FAIL);
        }
    }
    return storeFileResult;
}

 

 

Rollback을 발생시키기 위해 CheckedException을 try - catch 문으로 받고, UnCheckedException을 발생시켜서 해당 예외를 GlobalExceptionHandler 클래스에서 처리하면 되겠다

 

[FileSaveFailedException 클래스는 RuntimeException 클래스를 상속하여 만든 커스텀한 클래스]

 

요렇게 생각을 했었지만,,,, 

 

 

이후에 공식문서를 읽으면서

 

하지만,,, 이후에 공식문서를 읽는 중,,, 내가 "공식문서에서 하지 말라는 행동"을 하고 있었다.

 

Java Docs에서

자바 프로그래밍 언어는 메소드가 unchecked exception (RuntimeException, Error 및 그 서브클래스)을 잡거나 명시할 필요가 없기 때문에, 프로그래머들은 unchecked exception만을 던지거나 모든 예외 서브클래스를 에서 상속받게 하는 코드를 작성하는 유혹에 빠질 수 있습니다. 이러한 단축 방법은 프로그래머가 컴파일러 오류에 신경 쓰지 않고, 어떠한 예외도 명시하거나 잡을 필요 없이 코드를 작성할 수 있게 합니다.

디자이너들이 메소드 내에서 던질 수 있는 모든 uncaught checked exception을 명시하도록 강제한 이유는 무엇일까요?

메소드에 의해 던져질 수 있는 모든 예외는 그 메소드의 공개 프로그래밍 인터페이스의 일부입니다. 메소드를 호출하는 사람들은 메소드가 던질 수 있는 예외에 대해 알아야 하므로, 그들이 이에 대해 어떻게 할지 결정할 수 있습니다. 이러한 예외들은 그 메소드의 프로그래밍 인터페이스의 일부로, 그것의 매개변수와 반환 값만큼 중요합니다.

 

 

그래,,, 굳이 CheckedException을 발생시키는 이유가 있었구나

 

 

진짜 해결

 

CheckedException이 발생하는 것을 명시하는 것이 더 낫겠다고 생각했다. 

따라서, 아래와 같이 코드를 수정하였다.

 

public List<UploadFile> storeFiles(List<MultipartFile> multipartFiles) throws IOException {
    List<UploadFile> storeFileResult = new ArrayList<>();
    for (MultipartFile multipartFile : multipartFiles) {
        storeFileResult.add(storeFile(multipartFile));
    }
    return storeFileResult;
}

@Transactional(rollbackFor = IOException.class)
public List<Certificate> saveCertifications(List<MultipartFile> certificates) throws IOException {
    List<UploadFile> uploadFiles = fileUtils.storeFiles(certificates);
    List<Certificate> certificateList = CertificateMapper.to(uploadFiles);
    certificateRepository.saveAll(certificateList);
    return certificateList;
}

 

즉, 해당 예외를 명시적으로 보이게 하기 위해서 @Transactional의 rolbackFor 속성을 사용하는 방식으로 변경했다.

 

 


 

 

예외(Exception)

 

 

 

 

예외(exception)는 컴파일 시에 발생하는 예외 런타임 시 발생하는 예외로 나눌 수 있다.

 

컴파일 시 발생하는 예외

컴파일 시 발생하는 예외는 Exception 클래스를 상속받는 클래스 중 RunTimeException을 제외한 클래스들이다.

 

컴파일 시 발생하는 예외는 IOException등이 있다.

 

이러한 예외들을 CheckedException이라고 한다.

즉, 명시적으로 예외 처리를 해줘야하는 예외라는 뜻이다.

 

public class IOException extends Exception {
	public IOException() {
        super();
    }
    ...
 }

 

 

 

 

 

감사합니다!

 

 

https://docs.oracle.com/javase/tutorial/essential/exceptions/throwing.html

 

How to Throw Exceptions (The Java™ Tutorials > Essential Java Classes > Exceptions)

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com

https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html

 

Unchecked Exceptions — The Controversy (The Java™ Tutorials > Essential Java Classes > Exceptions)

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com

 

728x90