스프린트 4 완료 후, 멘토님한테서 이런 피드백을 받았다:

내 코드는 이렇게 쓰여져 있었다:
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
@Component
public class BinaryContentMapper {
// DTO를 Entity로 변환하는 메소드
public BinaryContent toBinaryContent(BinaryContentRequestDto binaryContentRequestDto) {
return new BinaryContent(
binaryContentRequestDto.userId(),
binaryContentRequestDto.messageId(),
binaryContentRequestDto.bytes(),
binaryContentRequestDto.fileName(),
binaryContentRequestDto.fileType()
);
}
// MultipartFile을 Entity로 변환하는 메소드
public BinaryContent toBinaryContent(UUID userId, UUID messageId, MultipartFile file) {
if (file == null || file.isEmpty()) {
return null;
}
try {
return new BinaryContent(
userId,
messageId,
file.getBytes(),
file.getOriginalFilename(),
file.getContentType()
);
} catch (IOException e) {
throw new RuntimeException("파일 바이트를 읽는 중 오류가 발생했습니다.", e);
}
}
}
하지만 RuntimeException을 IOException으로 바꿔준다면
- 먼저, 이 매퍼 클래스를 호출하는 모든 상위 레이어에도 IOException을 붙혀주어야 했다 (너무 번거로웠다 ㅠㅠ)
- IOException은 오직 레포지토리에서만 발생을 하므로, 상위 계층인 서비스 레이어는 알아서는 안 될 예외다
- 나중에 @Transactional 어노테이션을 사용할때 골치가 아파진다.
@Transactional과 예외처리
@Transactional 어노테이션은 unchecked exception들을 잡는데, IOException은 checked exception이다.
- 중간에 이 매퍼 클래스를 호출한 상위 계층에 @Transaction 어노테이션이 붙고 어떠한 에러가 생겨 롤백을 해야 할때, 이 에러가 만약에 IOException같은 checked exception이면, 이 에러가 중간에 발생해도 롤백을 하지 않는다!
여기서 간단하게 checked, unchecked exception을 비교하자면:
- checked - 개발자가 직접 핸들링
- unchecked - 런타임
GlobalExceptionHandler
그래서 결국 sprint5로 넘어가면서 따로 GlobalExceptionHandler 클래스를 만들어주었다.
- GlobalExceptionHandler(@RestControllerAdvice)는 애플리케이션 전역에서 발생한 예외들을 중간에 가로채서 처리한다
- 또한 어떤 exception에 어떠한 response를 보낼지 커스터마이징이 가능하다
@Hidden // swagger과 restcontrolleradvice 같이 사용하고 싶어서 추가
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NoSuchElementException.class)
public ResponseEntity<ErrorResponse> handleException(NoSuchElementException e) {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.NOT_FOUND.value(), e.getMessage());
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(errorResponse);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleException(IllegalArgumentException e) {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), e.getMessage());
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(errorResponse);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception e) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(e.getMessage());
}
@ExceptionHandler(FileAccessException.class)
public ResponseEntity<String> handleFileAccessException(FileAccessException e) {
return ResponseEntity
.status(e.getStatus())
.body(e.getMessage());
}
}
또한 매퍼 부분도 이렇게 수정이 되었다.
@Component
public class BinaryContentMapper {
public BinaryContentResponse toResponseDto(BinaryContent binaryContent) {
return new BinaryContentResponse(
binaryContent.getId(),
binaryContent.getCreatedAt(),
binaryContent.getFileName(),
binaryContent.getSize(),
binaryContent.getContentType(),
Base64.getEncoder().encodeToString(binaryContent.getBytes())
);
}
}
이 경험 덕분에 @Transaction과 exception에 대해서 더 자세히 공부할 수 있었다.
'Codeit > 스프린트 과제' 카테고리의 다른 글
| [sprint8] S3 관련 코드 + S3Client와 Presigned Url (3) | 2025.08.25 |
|---|---|
| [sprint8] 어플리케이션 컨테이너화 - dockerfile, docker compose, docker volume (1) | 2025.08.22 |
| [sprint5] 과제에서 사용한 swagger 간단하게 정리 (0) | 2025.07.14 |
| [sprint3] 심화 2 - application.yaml로 Bean 구현하기 (File*Repository 구현체의 파일 저장 경로 설정하기 (2) | 2025.06.28 |
| [sprint3] 심화 1 - application.yaml로 Repository 구현체 선택하기 (File/JCF) (2) | 2025.06.27 |