[SPRING] Memo 프로젝트 - DTO 설정, Mapping, API 설계, HttpStatus(에러처리)

림민지·2025년 3월 20일

Today I Learn

목록 보기
29/62

🍃 Spring 시작 가이드

인텔리제이에서 new project

GeneratorsSpring Boot로 설정하고
Language는 Java, Gradle-Groovy
JDK는 Amazon Corretto로 설정후 next!

Dependency에 Spring web, Lombok, Thymleaf 추가하기


📝 Memo 프로젝트 만들기

요구사항

:항상 프로젝트 시작 전에 요구사항을 정의하는 것이 중요하다

  1. 통신 데이터 형태는 JSON
  2. 각각의 메모는 식별자(id), 제목(title), 내용(contents)으로 구성
  3. 메모 CRUD 기능 필요
생성조회수정삭제
CRUDPOSTGETPUTDELETE
URL/memos/memos/{id}/memos/{id}/memos/{id}

1. Memo 클래스

import lombok.Getter;

@Getter
@AllArgsConstructor
public class Memo {
    private Long id;
    private String title;
    private String contents;
}

Memo 생성자를 만들고, @Getter 어노테이션으로 게터 자동으로 만들어주기
Long타입은 null을 받을 수 있으므로 Long으로 id 설정

@AllArgsConstructor
: 모든 필드를 초기화하는 생성자를 자동으로 만들어 줌
this.~ 이런거 안해도 알아서 해주는거!

2. DTO - (1) MemoRequestDto

: 요청 데이터를 처리하는 객체는 보통 RequestDto를 사용한다.

import lombok.Getter;
@Getter
public class MemoRequestDto {
    private String title;
    private String contents;
}

요청 데이터는 title과 contents로 구성하자

3. DTO - (2) MemoResponseDto

: Client에 데이터를 반환할 때 사용

import lombok.Getter;
@Getter
public class MemoResponseDto {
    private Long id;
    private String title;
    private String contents;
}

4. MemoController

이제 메모를 제어하는 Controller 클래스를 만들어보자
아까 정의했던대로, 생성/읽기/수정/삭제 기능이 되어야한다.

메모 데이터를 메모리에 저장할 memoList를 Map으로 만들자
private final Map<Long, Memo> memoList = new HashMap<>();

(1) 메모 생성 PostMapping

@RestController
@RequestMapping("/memos") // prefix
public class MemoController {

    // 자료구조가 DB 역할 수행
    private final Map<Long, Memo> memoList = new HashMap<>();

    @PostMapping 
    public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
        // 식별자가 1씩 증가 하도록 만듦
        Long memoId = memoList.isEmpty() ? 1 : Collections.max(memoList.keySet()) + 1;

        // 요청받은 데이터로 Memo 객체 생성
        Memo memo = new Memo(memoId, requestDto.getTitle(), requestDto.getContents());

        // Inmemory DB에 Memo 저장
        memoList.put(memoId, memo);

        return new MemoResponseDto(memo);
    }

}

(2) 메모 조회 GetMapping

@GetMapping("/{id}")
public MemoResponseDto findMemoById(@PathVariable Long id) {
    Memo memo = memoList.get(id);
    return new MemoResponseDto(memo);
}

(3) 메모 수정 PutMapping
일치하는 id를 조회한 후 그 아이디에 맞는 메모 update메서드를 활용해 수정한다.

Memo 클래스에 업데이트 메서드 추가 - 요청 dto를 활용해서

public void update(MemoRequestDto requestDto) {
    this.title = requestDto.getTitle();
    this.contents = requestDto.getContents();
}

update(변경할 값 넣기) 하면 업데이트가 되겠죠

@PutMapping("/{id}") // URL 경로에 {id} 라는 값을 포함
public MemoResponseDto updateMemoById(
        @PathVariable Long id,       // URL 경로의 id 값을 받아옴
        @RequestBody MemoRequestDto dto) {  // 요청 바디의 JSON 데이터를 받아옴

    Memo memo = memoList.get(id);  // 받아온 id로 Memo 객체 조회
    memo.update(dto);              // Memo 객체를 수정

    return new MemoResponseDto(memo);  // 수정된 Memo 객체를 응답으로 보냄
}

여기서, @PathVariable 이란?
: URL 경로(Path)에 포함된 값을 변수로 받아오는 어노테이션
→ URL 자체에 포함된 값을 메서드의 파라미터로 사용하는 방법!

🔍 사용법
URL 경로에서 {} 중괄호로 감싸서 경로 정의
(변수처럼 값 받아오기)

@PutMapping("/memos/{id}")
{id}는 경로의 일부로 사용되지만, 이 값은 동적으로 변경될 수 있음
예를 들어, 클라이언트가 요청을 보낼 때:
PUT /memos/3
여기서 3이라는 값이 @PathVariable Long id로 전달

원하는 id를 골라서 수정할 수 있겠죠??
그럼 이제 비슷한 원리로 DELETE도 구현해보자

(4) 메모 삭제 DeleteMapping

@DeleteMapping("/{id}")
public void deleteMemo(
        @PathVariable Long id
) {
    memoList.remove(id);
}

마찬가지로 id로 삭제할 메모를 찾고, 리스트에서 삭제한다
1번 메모를 삭제하고 다시 1번 메모를 조회하게 되면 아래처럼 500번대 에러가 생기는 것을 볼 수 있다! (NullPointerExecption이 발생)


📞 응답 코드 세분화 ResponseEntity<>

원래는 MemoResponseDto 객체를 직접 반환하고 있었다.
→ Spring은 자동으로 HTTP 상태 코드 200 OK를 반환한다
➡️ 성공했을 때는 괜찮지만, 다양한 상황에 따라 응답 상태 코드를 세밀하게 설정할 수 없다는 문제가 있다ㅜㅜ

ResponseEntity<MemoResponseDto>로 변경!

//원래 코드
@PostMapping
    public MemoResponseDto createMemo(@RequestBody MemoRequestDto dto){
    	...생략
   		return new MemoResponseDto(memo);

//개선 코드
@PostMapping
    public ResponseEntity<MemoResponseDto> createMemo(@RequestBody MemoRequestDto dto){
    
   ...생략
   return new ResponseEntity<>(new MemoResponseDto(memo), HttpStatus.CREATED);

원래 MemoResponseDto로 받던걸 ResponseEntity<>로 변경했다
그리고 반환값도 지정해주어야하므로 HttpStatus.CREATED를 붙여서 상태코드를 명시할 수 있게 해줬다

그러면 이렇게 201 Created라고 명시되는 모습을 볼 수 있다!

👍 장점
1. 클라이언트에게 더 의미 있는 응답 상태 코드를 전달
2. 단순히 데이터를 반환하는 것뿐만 아니라, 요청의 성공/실패를 명확하게 나타낼 수 있다
3. 응답 헤더를 추가 또는 설정 가능하다
4. 에러처리 및 다양한 응답 처리의 일관성을 제공한다
➡️ 즉, API 설계 원칙에 따라 더 의미 있는 응답 상태 코드와 유연한 설정을 제공한다!!

🥔 HashMap → List로 변환하기

메모 내용 저장을 이렇게 해쉬맵으로 저장했었다.
private final Map<Long, Memo> memoList = new HashMap<>();

메모를 조회할때 한번에 다 조회하고 싶어서 전체 조회할 때 쓸 수 있는 리스트를 만들어보려고 한다.
근데 저장이 해쉬맵으로 되어있으니까 리스트로 넘기려면 어떤 과정이 필요하다!

  1. 먼저 응답할 리스트를 만들어주자.
    MemoResponseDto 형식으로 리스트를 만들자
    List<MemoResponseDto> responseList = new ArrayList<>();

  2. 이제 해쉬맵에서 리스트로 변환해보자
    memoList 내부에 있는 value들을 꺼내서 다시 넣어주면 된다

for (Memo memo : memoList.values()){
    	MemoResponseDto responseDto = new MemoResponseDto(memo);
        responseList.add(responseDto);
        }

스트림을 활용해서 1줄로 이렇게 표현할 수도 있다
responseList = memoList.values().stream().map(MemoResponseDto::new).toList();

  1. postman 조회

이렇게 리스트 형태로 잘 조회되는 것을 확인할 수 있다!

profile
@lim_128

0개의 댓글