[SpringBoot] 2024 게시판 만들기 실습 ⑥ - CommonResponse & 예외처리

SihoonCho·2024년 11월 22일
0
post-thumbnail

※ 읽기에 앞서


본 시리즈는 작성자의 이해와 경험을 바탕으로 실습 위주의 설명을 제공하고자 작성되었습니다.
실습 중심의 이해를 목표로 작성되었기 때문에, 다소 과장되거나 생략된 부분이 있을 수 있습니다.
따라서, 이론적으로 미흡한 부분이 있을 수 있는 점에 유의하시고 양해 부탁드립니다.

또한, 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


CommonResponse는 API 응답 형식을 표준화한 응답 객체입니다.

  • 클라이언트와 서버 간 일관성 제공
  • 가독성과 유지보수성 향상
    • 성공 응답 및 에러 응답 구분
    • 상태 코드, 메시지, 데이터 포함

이를 통해 각 API에서 통일된 구조의 반환 형식을 사용할 수 있습니다.

이번 글에서는 기존 PostControllerCommonResponse를 적용하여
최적화 및 성능 개선을 해보겠습니다.

PostController 최적화 및 성능 개선

  • PostControllerPostControllerV1 클래스명 변경
  • PostControllerV2: 최적화 및 개선된 컨트롤러 추가
    • URL Mapping 최적화 → RequestMapping 적용
    • CommonResponse 적용

📖 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 응답 형식을 표준화한 응답 객체입니다.

  • 성공 응답 및 에러 응답 구분
  • 상태 코드, 메시지, 데이터 포함

📖 PostControllerV1


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 최적화 및 성능 개선

  • PostControllerPostControllerV1 클래스명 변경

📖 PostControllerV2


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 적용

📖 CRController API 테스트


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 구현

📖 CustomNotFoundException


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 예외 클래스 상속
    • 재사용성 및 코드 가독성 향상

📖 CustomValidateException


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


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


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를 활용해 에러 응답 형식을 표준화
  • 보안을 위해 발생한 예외 정보를 응답하는 대신 서버 로그로 남김
  • 반드시 상속 관계를 고려해 구체적인 예외(하위 객체)를 먼저 처리
  • 반드시 상속 관계를 고려해 일반적인 예외(상위 객체)를 나중에 처리

📖 PostReadServiceImpl


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();

📖 PostWriteServiceImpl


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와 관련된 내용은 별도의 참고자료를 검색/활용하실 것을 권장드립니다.
profile
개발을 즐길 줄 아는 백엔드 개발자

0개의 댓글