본 시리즈는 작성자의 이해와 경험을 바탕으로 실습 위주의 설명을 제공하고자 작성되었습니다.
실습 중심의 이해를 목표로 작성되었기 때문에, 다소 과장되거나 생략된 부분이 있을 수 있습니다.
따라서, 이론적으로 미흡한 부분이 있을 수 있는 점에 유의하시고 양해 부탁드립니다.
또한, Spring Boot 기반의 Backend 개발에 중점을 두고 설명하고 있으므로,
Frontend와 관련된 내용은 별도의 참고자료를 검색/활용하실 것을 권장드립니다.
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com.companyname.projectname
│ │ │ ├── common // 패키지 추가
│ │ │ │ ├── controller // 패키지 추가
│ │ │ │ │ └── CommonResponseController.java // 클래스 추가
│ │ │ │ ├── dto // 패키지 추가
│ │ │ │ │ └── CommonResponse.java // 클래스 추가
│ │ │ │ ├── exception // 패키지 추가
│ │ │ │ │ ├── CustomNotFoundException.java // 클래스 추가
│ │ │ │ │ └── CustomValidateException.java // 클래스 추가
│ │ │ │ └── handler // 패키지 추가
│ │ │ │ ├── CustomExceptionHandler.java // 클래스 추가
│ │ │ │ └── GlobalExceptionHandler.java // 클래스 추가
│ │ │ ├── post
│ │ │ │ ├── controller
│ │ │ │ │ ├── PostControllerV1.java // 클래스 수정
│ │ │ │ │ └── PostControllerV2.java // 클래스 추가
│ │ │ │ ├── dto
│ │ │ │ │ ├── PostCreateRequestDto.java
│ │ │ │ │ ├── PostUpdateRequestDto.java
│ │ │ │ │ ├── PostDetailResponseDto.java
│ │ │ │ │ └── PostListResponseDto.java
│ │ │ │ ├── model
│ │ │ │ │ └── Post.java
│ │ │ │ ├── repository
│ │ │ │ │ └── PostRepository.java
│ │ │ │ └── service
│ │ │ │ ├── PostReadService.java
│ │ │ │ ├── PostReadServiceImpl.java
│ │ │ │ ├── PostWriteService.java
│ │ │ │ └── PostWriteServiceImpl.java
│ │ │ └── ProjectNameApplication.java
│ │ └── resources
│ └── test
CommonResponse
는 API 응답 형식을 표준화한 응답 객체입니다.
- 클라이언트와 서버 간 일관성 제공
- 가독성과 유지보수성 향상
- 성공 응답 및 에러 응답 구분
- 상태 코드, 메시지, 데이터 포함
이를 통해 각
API
에서 통일된 구조의 반환 형식을 사용할 수 있습니다.
이번 글에서는 기존
PostController
에CommonResponse
를 적용하여
최적화 및 성능 개선을 해보겠습니다.
PostController 최적화 및 성능 개선
PostController
→PostControllerV1
클래스명 변경PostControllerV2
: 최적화 및 개선된 컨트롤러 추가
URL Mapping
최적화 →RequestMapping
적용CommonResponse
적용
package com.companyname.projectname.common.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CommonResponse<T> {
private String message;
private int status;
private T data;
// Success Response
public static CommonResponse<Void> success() {
return CommonResponse.<Void>builder()
.message("The request was successful")
.status(200)
.build();
}
public static <T> CommonResponse<T> success(T data) {
return CommonResponse.<T>builder()
.message("The request was successful")
.status(200)
.data(data)
.build();
}
public static <T> CommonResponse<T> success(String message, T data) {
return CommonResponse.<T>builder()
.message(message)
.status(200)
.data(data)
.build();
}
public static <T> CommonResponse<T> success(int status, String message, T data) {
return CommonResponse.<T>builder()
.message(message)
.status(status)
.data(data)
.build();
}
// Error Response
public static CommonResponse<Void> error() {
return CommonResponse.<Void>builder()
.message("Internal Server Error: An unexpected error occurred.")
.status(500)
.build();
}
public static CommonResponse<Void> error(int status) {
String message = switch (status) {
case 400 -> "Bad Request: The request is invalid.";
case 401 -> "Unauthorized: Authentication is required.";
case 403 -> "Forbidden: Access to the resource is denied.";
case 404 -> "Not Found: The requested resource could not be found.";
case 500 -> "Internal Server Error: An unexpected error occurred.";
default -> "Unexpected Error: An unknown error occurred. Please contact support.";
};
return CommonResponse.<Void>builder()
.message(message)
.status(status)
.build();
}
public static CommonResponse<Void> error(int status, String message) {
return CommonResponse.<Void>builder()
.message(message)
.status(status)
.build();
}
public static <T> CommonResponse<T> error(int status, String message, T data) {
return CommonResponse.<T>builder()
.message(message)
.status(status)
.data(data)
.build();
}
}
CommonResponse
는 API 응답 형식을 표준화한 응답 객체입니다.
- 성공 응답 및 에러 응답 구분
- 상태 코드, 메시지, 데이터 포함
package com.companyname.projectname.post.controller;
import com.companyname.projectname.post.dto.PostCreateRequestDto;
import com.companyname.projectname.post.dto.PostDetailResponseDto;
import com.companyname.projectname.post.dto.PostListResponseDto;
import com.companyname.projectname.post.dto.PostUpdateRequestDto;
import com.companyname.projectname.post.service.PostReadService;
import com.companyname.projectname.post.service.PostWriteService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequiredArgsConstructor
public class PostControllerV1 {
private final PostReadService postReadService;
private final PostWriteService postWriteService;
@GetMapping("/api/v1/post/list")
public List<PostListResponseDto> getOverviewPosts() {
return postReadService.findAllLists();
}
@GetMapping("/api/v1/post/list/detail")
public List<PostDetailResponseDto> getDetailedPosts() {
return postReadService.findAllDetails();
}
@GetMapping("/api/v1/post/{id}")
public PostDetailResponseDto getPost(@PathVariable Long id) {
return postReadService.findById(id);
}
@PostMapping("/api/v1/post")
public PostDetailResponseDto createPost(@RequestBody PostCreateRequestDto requestDto) {
return postWriteService.create(requestDto);
}
@PutMapping("/api/v1/post")
public PostDetailResponseDto updatePost(@RequestBody PostUpdateRequestDto requestDto) {
return postWriteService.update(requestDto);
}
@DeleteMapping("/api/v1/post/{id}")
public void deletePost(@PathVariable Long id) {
postWriteService.delete(id);
}
}
PostControllerV1 최적화 및 성능 개선
PostController
→PostControllerV1
클래스명 변경
package com.companyname.projectname.post.controller;
import com.companyname.projectname.common.dto.CommonResponse;
import com.companyname.projectname.post.dto.PostCreateRequestDto;
import com.companyname.projectname.post.dto.PostDetailResponseDto;
import com.companyname.projectname.post.dto.PostListResponseDto;
import com.companyname.projectname.post.dto.PostUpdateRequestDto;
import com.companyname.projectname.post.service.PostReadService;
import com.companyname.projectname.post.service.PostWriteService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v2/post")
public class PostControllerV2 {
private final PostReadService postReadService;
private final PostWriteService postWriteService;
@GetMapping("/list")
public ResponseEntity<CommonResponse<?>> getOverviewPosts() {
List<PostListResponseDto> responseDto = postReadService.findAllLists();
return ResponseEntity.status(HttpStatus.OK).body(CommonResponse.success(responseDto));
}
@GetMapping("/list/detail")
public ResponseEntity<CommonResponse<?>> getDetailedPosts() {
List<PostDetailResponseDto> responseDto = postReadService.findAllDetails();
return ResponseEntity.status(HttpStatus.OK).body(CommonResponse.success(responseDto));
}
@GetMapping("/{id}")
public ResponseEntity<CommonResponse<?>> getPost(@PathVariable Long id) {
PostDetailResponseDto responseDto = postReadService.findById(id);
return ResponseEntity.status(HttpStatus.OK).body(CommonResponse.success(responseDto));
}
@PostMapping
public ResponseEntity<CommonResponse<?>> createPost(@RequestBody PostCreateRequestDto requestDto) {
PostDetailResponseDto responseDto = postWriteService.create(requestDto);
return ResponseEntity.status(HttpStatus.OK).body(CommonResponse.success(responseDto));
}
@PutMapping
public ResponseEntity<CommonResponse<?>> updatePost(@RequestBody PostUpdateRequestDto requestDto) {
PostDetailResponseDto responseDto = postWriteService.update(requestDto);
return ResponseEntity.status(HttpStatus.OK).body(CommonResponse.success(responseDto));
}
@DeleteMapping("/{id}")
public ResponseEntity<CommonResponse<?>> deletePost(@PathVariable Long id) {
postWriteService.delete(id);
return ResponseEntity.status(HttpStatus.OK).body(CommonResponse.success());
}
}
PostControllerV2 최적화 및 성능 개선
PostControllerV2
: 최적화 및 개선된 컨트롤러
URL Mapping
최적화 →RequestMapping
적용CommonResponse
적용
package com.companyname.projectname.common.controller;
import com.companyname.projectname.common.dto.CommonResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/common-response")
public class CommonResponseController {
private static final int NON_STANDARD_SUCCESS_CODE = 700;
private static final int NON_STANDARD_FAILURE_CODE = 800;
private static final String NON_STANDARD_SUCCESS_MSG = "Nonstandard success response message.";
private static final String NON_STANDARD_FAILURE_MSG = "Nonstandard failure response message.";
private static final Map<String, String> dummyData = Map.of(
"key1", "value1",
"key2", "value2",
"key3", "value3"
);
// Success REST API
@GetMapping("/success")
public ResponseEntity<CommonResponse<?>> success() {
return ResponseEntity.status(HttpStatus.OK)
.body(CommonResponse.success());
}
@GetMapping("/success/data")
public ResponseEntity<CommonResponse<?>> successWithData() {
return ResponseEntity.status(HttpStatus.OK)
.body(CommonResponse.success(dummyData));
}
@GetMapping("/success/message-data")
public ResponseEntity<CommonResponse<?>> successWithMessageAndData() {
return ResponseEntity.status(HttpStatus.OK)
.body(CommonResponse.success("Custom Message", dummyData));
}
@GetMapping("/success/custom")
public ResponseEntity<CommonResponse<?>> successWithCustom() {
return ResponseEntity.status(NON_STANDARD_SUCCESS_CODE)
.body(CommonResponse.success(NON_STANDARD_SUCCESS_CODE, NON_STANDARD_SUCCESS_MSG, dummyData));
}
// Error REST API
@GetMapping("/error")
public ResponseEntity<CommonResponse<?>> error() {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(CommonResponse.error());
}
@GetMapping("/error/status/{status}")
public ResponseEntity<CommonResponse<?>> errorWithStatus(@PathVariable Integer status) {
return ResponseEntity.status(status)
.body(CommonResponse.error(status));
}
@GetMapping("/error/custom")
public ResponseEntity<CommonResponse<?>> errorWithCustom() {
return ResponseEntity.status(NON_STANDARD_FAILURE_CODE)
.body(CommonResponse.error(NON_STANDARD_FAILURE_CODE, NON_STANDARD_FAILURE_MSG));
}
@GetMapping("/error/detail")
public ResponseEntity<CommonResponse<?>> errorWithDetail() {
return ResponseEntity.status(NON_STANDARD_FAILURE_CODE)
.body(CommonResponse.error(NON_STANDARD_FAILURE_CODE, NON_STANDARD_FAILURE_MSG, dummyData));
}
}
Exception
Exception
: 컴파일 시 발생하는 검사 예외(Checked Exception) 클래스
- 컴파일 시 예외 처리 요구, 처리하지 않으면 프로그램이 컴파일되지 않음
- Ex)
IOException
,SQLException
RuntimeException
: 런타임 시 발생하는 비검사 예외(Unchecked Exception) 클래스
- 런타임(실행 중) 시 발생하며, 예외를 처리하지 않으면 프로그램 중단
- 컴파일 시에는 오류 발생하지 않음, 실행 중 예외 발생 전까지는 프로그램 정상 작동
- Ex)
NullPointerException
,IllegalArgumentException
CustomException.class
: 자주 발생하는 예외를 별도로 정의한 사용자 정의 예외 클래스
- 예외를 표준화하여 프로젝트의 안정성과 유지보수성을 개선
RuntimeException
예외 클래스 상속- 재사용성 및 코드 가독성 향상
- Ex)
CustomNotFoundException
,CustomValidateException
ExceptionHandler
@RestControllerAdvice
를 사용해 프로젝트 전역에서 발생하는 예외 처리CommonResponse
를 활용해 에러 응답 형식을 표준화- 보안을 위해 발생한 예외 정보를 응답하는 대신 서버 로그로 남김
- 반드시 상속 관계를 고려해 구체적인 예외(하위 객체)를 먼저 처리
- 반드시 상속 관계를 고려해 일반적인 예외(상위 객체)를 나중에 처리
CustomException
- 자주 발생하는 예외를 별도로 정의한 사용자 정의 예외 클래스
- 예외를 표준화하여 프로젝트의 안정성과 유지보수성을 개선
RuntimeException
예외 클래스 상속- 재사용성 및 코드 가독성 향상
- Ex)
CustomNotFoundException
,CustomValidateException
CustomExceptionHandler
- 전역적으로 예외를 처리하며, 사용자 정의 오류에 대한 표준화된 응답 제공
- 사용자 정의 예외를 서버 로그에 남겨 디버깅 및 유지보수 효율성 향상
- 보안을 위해 상태 코드와 추상적인 예외 메시지를 클라이언트에 전달
GlobalException
- 프로젝트 전반에서 발생하는 예상치 못한 예외를 포괄적으로 처리하는 상위 예외 클래스
Exception
: 컴파일 시 발생하는 검사 예외(Checked Exception) 클래스
- 컴파일 시 예외 처리 요구, 처리하지 않으면 프로그램이 컴파일되지 않음
- Ex)
IOException
,SQLException
RuntimeException
: 런타임 시 발생하는 비검사 예외(Unchecked Exception) 클래스
- 런타임(실행 중) 시 발생하며, 예외를 처리하지 않으면 프로그램 중단
- 컴파일 시에는 오류 발생하지 않음, 실행 중 예외 발생 전까지는 프로그램 정상 작동
- Ex)
NullPointerException
,IllegalArgumentException
GlobalExceptionHandler
- 전역적으로 예외를 처리하며, 예기치 못한 오류에 대한 표준화된 응답 제공
- 모든 예외를 서버 로그에 남겨 디버깅 및 유지보수 효율성 향상
- 보안을 위해 상태 코드와 추상적인 예외 메시지를 클라이언트에 전달
CustomException 및 ExceptionHandler 구현
CustomException
구현
CustomNotFoundException
구현CustomValidateException
구현CustomExceptionHandler
구현GlobalExceptionHandler
구현
package com.companyname.projectname.common.exception;
public class CustomNotFoundException extends RuntimeException {
public CustomNotFoundException() {
super("The requested resource could not be found.");
}
public CustomNotFoundException(String message) {
super(message);
}
public CustomNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
CustomException
- 자주 발생하는 예외를 별도로 정의한 사용자 정의 예외 클래스
- 예외를 표준화하여 프로젝트의 안정성과 유지보수성을 개선
RuntimeException
예외 클래스 상속- 재사용성 및 코드 가독성 향상
package com.companyname.projectname.common.exception;
public class CustomValidateException extends RuntimeException {
public CustomValidateException() {
super("The request is invalid, validation failed.");
}
public CustomValidateException(String message) {
super(message);
}
public CustomValidateException(String message, Throwable cause) {
super(message, cause);
}
}
CustomException
- 자주 발생하는 예외를 별도로 정의한 사용자 정의 예외 클래스
- 예외를 표준화하여 프로젝트의 안정성과 유지보수성을 개선
RuntimeException
예외 클래스 상속- 재사용성 및 코드 가독성 향상
CustomExceptionHandler
- 전역적으로 예외를 처리하며, 사용자 정의 오류에 대한 표준화된 응답 제공
- 사용자 정의 예외를 서버 로그에 남겨 디버깅 및 유지보수 효율성 향상
- 보안을 위해 상태 코드와 추상적인 예외 메시지를 클라이언트에 전달
package com.companyname.projectname.common.handler;
import com.companyname.projectname.common.dto.CommonResponse;
import com.companyname.projectname.common.exception.CustomNotFoundException;
import com.companyname.projectname.common.exception.CustomValidateException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class CustomExceptionHandler {
@ExceptionHandler(CustomNotFoundException.class)
public ResponseEntity<CommonResponse<?>> handleCustomNotFoundException(CustomNotFoundException e) {
log.error("CustomExceptionHandler CustomNotFoundException occurred: {}", e.getMessage(), e);
return ResponseEntity.status(801)
.body(CommonResponse.error(801));
}
@ExceptionHandler(CustomValidateException.class)
public ResponseEntity<CommonResponse<?>> handleCustomValidateException(CustomValidateException e) {
log.error("CustomExceptionHandler CustomValidateException occurred: {}", e.getMessage(), e);
return ResponseEntity.status(802)
.body(CommonResponse.error(802));
}
}
ExceptionHandler
@RestControllerAdvice
를 사용해 프로젝트 전역에서 발생하는 예외 처리CommonResponse
를 활용해 에러 응답 형식을 표준화- 보안을 위해 발생한 예외 정보를 응답하는 대신 서버 로그로 남김
- 반드시 상속 관계를 고려해 구체적인 예외(하위 객체)를 먼저 처리
- 반드시 상속 관계를 고려해 일반적인 예외(상위 객체)를 나중에 처리
GlobalExceptionHandler
- 전역적으로 예외를 처리하며, 예기치 못한 오류에 대한 표준화된 응답 제공
- 모든 예외를 서버 로그에 남겨 디버깅 및 유지보수 효율성 향상
- 보안을 위해 상태 코드와 추상적인 예외 메시지를 클라이언트에 전달
package com.companyname.projectname.common.handler;
import com.companyname.projectname.common.dto.CommonResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.sql.SQLException;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<CommonResponse<?>> handleIllegalArgumentException(IllegalArgumentException e) {
log.error("GlobalExceptionHandler IllegalArgumentException occurred: {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(CommonResponse.error(400));
}
@ExceptionHandler(NullPointerException.class)
public ResponseEntity<CommonResponse<?>> handleNullPointerException(NullPointerException e) {
log.error("GlobalExceptionHandler NullPointerException occurred: {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(CommonResponse.error(500));
}
@ExceptionHandler(SQLException.class)
public ResponseEntity<CommonResponse<?>> handleSQLException(SQLException e) {
log.error("GlobalExceptionHandler SQLException occurred: {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(CommonResponse.error(500));
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<CommonResponse<?>> handleRuntimeException(RuntimeException e) {
log.error("GlobalExceptionHandler RuntimeException occurred: {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(CommonResponse.error(500));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<CommonResponse<?>> handleGeneralException(Exception e) {
log.error("GlobalExceptionHandler General Exception occurred: {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(CommonResponse.error(500));
}
}
ExceptionHandler
@RestControllerAdvice
를 사용해 프로젝트 전역에서 발생하는 예외 처리CommonResponse
를 활용해 에러 응답 형식을 표준화- 보안을 위해 발생한 예외 정보를 응답하는 대신 서버 로그로 남김
- 반드시 상속 관계를 고려해 구체적인 예외(하위 객체)를 먼저 처리
- 반드시 상속 관계를 고려해 일반적인 예외(상위 객체)를 나중에 처리
package com.companyname.projectname.post.service;
import com.companyname.projectname.common.exception.CustomNotFoundException;
import com.companyname.projectname.post.dto.PostDetailResponseDto;
import com.companyname.projectname.post.dto.PostListResponseDto;
import com.companyname.projectname.post.model.Post;
import com.companyname.projectname.post.repository.PostRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PostReadServiceImpl implements PostReadService {
private final PostRepository postRepository;
@Override
public PostDetailResponseDto findById(Long id) {
Post entity = postRepository.findById(id).orElseThrow(
() -> new CustomNotFoundException("Post not found with id: " + id));
return new PostDetailResponseDto(entity);
}
// ... 이하 변경 없음 ...
}
유효성 검사 로직 추가
예제 코드를 참고하여 필요에 따라 응용하세요.
throw new CustomNotFoundException();
throw new CustomValidateException();
package com.companyname.projectname.post.service;
import com.companyname.projectname.common.exception.CustomNotFoundException;
import com.companyname.projectname.common.exception.CustomValidateException;
import com.companyname.projectname.post.dto.PostCreateRequestDto;
import com.companyname.projectname.post.dto.PostDetailResponseDto;
import com.companyname.projectname.post.dto.PostUpdateRequestDto;
import com.companyname.projectname.post.model.Post;
import com.companyname.projectname.post.repository.PostRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@RequiredArgsConstructor
public class PostWriteServiceImpl implements PostWriteService {
private final PostRepository postRepository;
@Override
public PostDetailResponseDto create(PostCreateRequestDto requestDto) {
if (requestDto.getTitle() == null || requestDto.getTitle().isEmpty())
throw new CustomValidateException("Post title must not be empty.");
if (requestDto.getContent() == null || requestDto.getContent().isEmpty())
throw new CustomValidateException("Post content must not be empty.");
return new PostDetailResponseDto(postRepository.save(requestDto.toEntity()));
}
@Override
public PostDetailResponseDto update(PostUpdateRequestDto requestDto) {
Post entity = postRepository.findById(requestDto.getId()).orElseThrow(
() -> new CustomNotFoundException("Post not found with id: " + requestDto.getId()));
return new PostDetailResponseDto(postRepository.save(entity.update(requestDto)));
}
@Override
public void delete(Long id) {
if (!postRepository.existsById(id))
throw new CustomNotFoundException("Cannot delete Post. Post not found with id: " + id);
postRepository.deleteById(id);
}
}
유효성 검사 로직 추가
예제 코드를 참고하여 필요에 따라 응용하세요.
throw new CustomNotFoundException();
throw new CustomValidateException();
본 시리즈는 작성자의 이해와 경험을 바탕으로 실습 위주의 설명을 제공하고자 작성되었습니다.
실습 중심의 이해를 목표로 작성되었기 때문에, 다소 과장되거나 생략된 부분이 있을 수 있습니다.
따라서, 이론적으로 미흡한 부분이 있을 수 있는 점에 유의하시고 양해 부탁드립니다.
또한, Spring Boot 기반의 Backend 개발에 중점을 두고 설명하고 있으므로,
Frontend와 관련된 내용은 별도의 참고자료를 검색/활용하실 것을 권장드립니다.