배경지식
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
https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html
'Project Trouble Shooting > [축팅] 축제 소개팅 어플리케이션 - 카카오 테크 캠퍼스 1기' 카테고리의 다른 글
카카오 테크 캠퍼스 수료 및 대상 🏆 (0) | 2023.11.19 |
---|---|
ThreadLocalRandom의 설계의도와 스레드의 관계 (1) | 2023.11.01 |
"Run all Tests"로 모든 단위테스트를 한번에 돌릴 때 실패하는 이슈 (0) | 2023.10.14 |
ComposeMethod을 적용해 리팩터링 해보자 (1) | 2023.10.13 |
Instant 클래스 도입에 관한 고찰 (0) | 2023.10.02 |