1. 학습 주제
Spring Boot에서 JSON 요청과 응답을 다루는 흐름, 그리고 API에서 예외를 어떻게 분리해서 표현하는지를 학습했다.
특히 핵심은 자바 객체와 JSON이 어떻게 자동 변환되는지, 그리고 정상 응답과 예외 상황을 어떻게 구분해서 처리할 준비를 하는지를 이해하는 데 있었다.
- List<MemberDTO>를 JSON 배열로 응답하기
- MemberDTO 한 건을 JSON 객체로 응답하기
- @RequestBody로 JSON 요청 본문을 자바 객체로 받기
- ResponseEntity로 상태 코드와 응답 본문 함께 제어하기
- Map<String, Object>로 message + data 구조 응답 만들기
- ObjectMapper로 JSON 변환 원리 직접 확인하기
- 사용자 정의 예외 클래스로 API 예외 상황 분리하기
2. Spring Boot에서 JSON이 자동으로 처리되는 흐름
이번 범위의 핵심은 이 흐름이다.
- 클라이언트가 HTTP 요청을 보낸다
- Spring Boot가 URL과 HTTP Method에 맞는 Controller 메서드를 찾는다
- 요청 body가 JSON이면 @RequestBody를 통해 자바 객체로 바꾼다
- Controller가 자바 객체나 리스트를 반환한다
- Spring Boot가 그 반환값을 다시 JSON으로 바꿔 응답한다
즉 개발자가 직접 JSON 문자열을 하나하나 만들지 않아도,
Spring Boot 내부에서 Jackson이 자바 객체와 JSON 사이 변환을 자동으로 처리해준다.
이 자동 변환 덕분에 컨트롤러에서는 “문자열 가공”보다
“어떤 객체를 받고 어떤 객체를 반환할지”에 더 집중할 수 있다.
3. List<MemberDTO> -> JSON 배열 응답
회원 목록 조회 메서드는 List<MemberDTO>를 그대로 반환했다.
@GetMapping
public ResponseEntity<List<MemberDTO>> getMembers() {
return ResponseEntity.ok(memberList);
}
이 코드의 의미는:
- 자바에서는 List<MemberDTO>를 반환하지만
- 응답 시점에는 JSON 배열로 변환된다
즉 자바 객체 구조와 JSON 구조가 자연스럽게 연결된다.
예를 들어 자바에서는:
- MemberDTO 객체 여러 개가 들어 있는 리스트
형태지만, 클라이언트는 이를:
- JSON 배열 안에 JSON 객체 여러 개가 들어 있는 형태
로 받게 된다.
이 부분이 중요한 이유는,
Spring Boot API 개발에서 가장 자주 다루는 응답 형태가 바로 “목록 조회”이기 때문이다.
4. MemberDTO 한 건 -> JSON 객체 응답
특정 회원 1명을 조회하는 메서드도 있었다.
@GetMapping("/{memberNo}")
public ResponseEntity<MemberDTO> findMember(@PathVariable int memberNo)
여기서 중요한 것은 두 가지다.
1. @PathVariable
URL 경로의 값을 변수처럼 받는다.
예:
- GET /api/v1/members/1
이면 1이 memberNo로 들어온다.
2. 객체 1개 반환
MemberDTO 한 개를 반환하면, 응답은 JSON 객체가 된다.
즉:
- 목록 조회는 JSON 배열
- 단건 조회는 JSON 객체
라는 차이를 자연스럽게 확인할 수 있었다.
5. JSON 요청 본문 -> MemberDTO
회원 등록 메서드에서는 @RequestBody를 사용했다.
@PostMapping
public ResponseEntity<Map<String, Object>> createMember(@RequestBody MemberDTO member)
이 부분이 아주 중요하다.
클라이언트가 이런 JSON을 보내면:
{
"no": 5,
"name": "오랑우탄",
"age": 4,
"enrollDate": "2026-05-29 10:35:00"
}
Spring Boot는 이를 자동으로 MemberDTO 객체로 바꿔준다.
즉 @RequestBody는
JSON 요청 본문을 자바 객체로 역직렬화하는 역할을 한다.
이전처럼 직접 request.getReader()로 본문을 읽고,
문자열을 파싱해서 객체를 만들지 않아도 된다는 점이 Spring Boot의 큰 장점이다.
6. 왜 기본 생성자와 getter/setter가 필요한가
DTO를 보면:
- 기본 생성자
- getter/setter
가 있다.
이게 필요한 이유는 Jackson이 JSON을 자바 객체로 바꿀 때:
- 기본 생성자로 객체를 만들고
- setter를 통해 값을 채워 넣기 때문이다
즉 DTO는 단순히 값만 담는 클래스처럼 보이지만,
JSON 변환을 위해서는 이런 기본 구조가 필요하다.
7. 날짜 포맷 제어와 @JsonFormat
MemberDTO의 날짜 필드에는 @JsonFormat이 붙어 있었다.
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private Date enrollDate;
이 어노테이션의 역할은:
- 날짜를 어떤 문자열 형태로 JSON에 넣을지 지정
- 시간대도 함께 지정
즉 날짜/시간 타입은 그냥 두면 원하는 모양으로 안 나올 수 있기 때문에,
클라이언트와 맞는 형식으로 출력되게 제어하는 것이다.
실무에서도 날짜 포맷은 매우 자주 맞춰야 하는 부분이라 중요하다.
다만 여기서는 패턴을 직접 지정하는 방식을 익히는 단계로 이해하면 된다.
8. ResponseEntity를 쓰는 이유
모든 응답이 단순 객체 반환이 아니라 ResponseEntity로 감싸져 있었다.
예:
return ResponseEntity.ok(memberList);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
ResponseEntity를 쓰면 다음을 함께 제어할 수 있다.
- 응답 본문(body)
- 상태 코드(status)
즉 단순 데이터 반환보다 더 명확한 API 응답을 만들 수 있다.
예를 들어:
- 목록 조회 성공 -> 200 OK
- 등록 성공 -> 201 Created
처럼 HTTP 의미를 정확히 표현할 수 있다.
API 설계에서는 이 상태 코드 표현이 꽤 중요하다.
9. message + data 응답 구조
회원 등록과 래퍼 응답 메서드에서는 Map<String, Object>를 사용했다.
Map<String, Object> response = new LinkedHashMap<>();
response.put("message", "회원 등록 요청 성공");
response.put("member", member);
또는:
response.put("message", "회원 목록 조회 성공");
response.put("count", memberList.size());
response.put("data", memberList);
이 구조의 의미는 단순히 데이터를 보내는 것보다
응답 의미를 더 풍부하게 담는 것이다.
즉 응답 JSON을 이렇게 만들 수 있다.
- message: 처리 결과 설명
- count: 목록 개수
- data: 실제 데이터 본문
이런 구조는 프론트엔드에서 다루기도 좋고,
API 응답 형식을 일정하게 맞추는 데도 유용하다.
10. LinkedHashMap을 사용한 이유
응답용 Map으로 LinkedHashMap을 사용한 것도 눈여겨볼 만하다.
이유는:
- 입력한 순서대로 키가 유지되기 때문이다
즉 응답 JSON에서:
- message
- count
- data
순서가 비교적 일정하게 보이도록 만들 수 있다.
기능상 꼭 필수는 아니지만, 응답 구조를 보기 좋게 유지하는 데 도움이 된다.
11. ObjectMapper를 직접 사용한 이유
/object-mapper 메서드에서는 ObjectMapper를 직접 사용했다.
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(memberList);
이 코드는 자바 객체를 직접 JSON 문자열로 바꾸는 예제다.
핵심은 이거다.
- 평소에는 Spring Boot가 자동으로 Jackson을 써서 JSON 변환을 해준다
- 이번 예제에서는 그 내부 원리를 직접 눈으로 확인하기 위해 ObjectMapper를 사용했다
즉 ObjectMapper는
Jackson에서 자바 객체 <-> JSON 변환을 직접 담당하는 핵심 도구다.
실무에서는 자동 변환에 많이 맡기지만,
직접 JSON 문자열이 필요할 때는 ObjectMapper를 쓸 수 있다.
12. 예외 처리 파트의 핵심 흐름
다음 범위에서는 예외 처리 구조를 분리하기 시작했다.
등장한 요소는 다음과 같다.
- MemberNotFoundException
- InvalidMemberRequestException
- orElseThrow(...)
회원 단건 조회 메서드에서는 이렇게 예외를 던졌다.
.orElseThrow(() -> new MemberNotFoundException(memberNo + "번 회원을 찾을 수 없습니다."));
이 흐름은 매우 중요하다.
즉 단순히 null을 반환하는 게 아니라:
- 찾는 데이터가 없으면
- 의미 있는 커스텀 예외를 발생시킨다
는 구조다.
13. 왜 사용자 정의 예외를 만드는가
MemberNotFoundException, InvalidMemberRequestException 같은 클래스를 따로 만드는 이유는
예외 상황의 의미를 더 분명하게 표현하기 위해서다.
예를 들어 그냥 RuntimeException만 쓰면:
- 어떤 문제인지 이름만 보고 알기 어렵다
하지만 MemberNotFoundException이면:
- 회원 조회 과정에서
- 대상이 없어서
- 실패했다는 점이 이름만으로도 드러난다
즉 사용자 정의 예외는
에러 상황을 비즈니스 의미에 맞게 분리하는 도구라고 이해하면 된다.
14. RuntimeException을 상속한 이유
두 예외 클래스 모두 RuntimeException을 상속하고 있었다.
이건 스프링 웹 개발에서 자주 보는 방식이다.
의미는:
- 예외를 반드시 try-catch로 처리하도록 강제하지 않고
- 필요한 지점에서 발생시킨 뒤
- 상위 계층이나 전역 예외 처리에서 다루기 쉽게 한다
즉 서비스나 컨트롤러 안에서 무조건 즉시 처리하는 방식보다,
“예외를 던지고 적절한 계층에서 응답으로 바꾸는 방식”에 더 어울린다.
15. 예외 처리 수준
- 커스텀 예외 클래스 분리
- 조회 실패 시 의미 있는 예외 던지기
즉 “예외를 분류하고 던지는 구조”를 먼저 익힌 단계라고 볼 수 있다.
아직 예외를 @ExceptionHandler나 @ControllerAdvice로
공통 응답 형태로 바꾸는 코드까지는 보이지 않았다.
그래서 이번 범위의 핵심은
예외 상황을 명확한 타입으로 구분해서 던지는 습관을 익히는 데 있다고 정리할 수 있다.
16. JSON 처리와 예외 처리를 함께 보면 좋은 이유
이 두 파트는 따로 보이지만 실제로는 연결된다.
API는 항상 두 가지를 모두 다뤄야 한다.
정상 흐름
- JSON 요청 받기
- 자바 객체로 변환
- 처리 후 JSON 응답 반환
비정상 흐름
- 데이터 없음
- 잘못된 요청
- 검증 실패
즉 API 개발은
성공 응답을 잘 만드는 것과
실패 상황을 의미 있게 표현하는 것을 같이 배워야 한다.
17. 학습 흐름 정리
17.1 JSON 처리
- @RestController 기반 API 응답
- List<MemberDTO> -> JSON 배열
- MemberDTO -> JSON 객체
- @RequestBody로 JSON 요청 받기
- ResponseEntity로 상태 코드 제어
- Map<String, Object>로 래퍼 응답 만들기
- ObjectMapper로 JSON 변환 원리 확인
17.2 예외 처리
- 데이터 조회 실패 시 orElseThrow() 사용
- MemberNotFoundException으로 의미 있는 예외 분리
- InvalidMemberRequestException으로 잘못된 요청 상황 대비
- RuntimeException 기반 커스텀 예외 구조 이해
18. 핵심 정리
- Spring Boot는 Controller 반환 객체를 Jackson을 통해 자동으로 JSON으로 변환해준다.
- List<MemberDTO>를 반환하면 JSON 배열, MemberDTO 한 개를 반환하면 JSON 객체가 된다.
- @RequestBody는 JSON 요청 본문을 자바 객체로 바꿔주는 핵심 어노테이션이다.
- DTO에는 기본 생성자와 getter/setter가 있어야 JSON 변환이 자연스럽게 동작한다.
- @JsonFormat을 사용하면 날짜 데이터를 원하는 문자열 형식으로 응답할 수 있다.
- ResponseEntity를 사용하면 body뿐 아니라 상태 코드도 함께 제어할 수 있다.
- Map<String, Object>를 사용하면 message, count, data 같은 래퍼 응답 구조를 만들 수 있다.
- ObjectMapper는 자바 객체와 JSON 문자열을 직접 변환하는 Jackson의 핵심 도구이다.
- orElseThrow()를 사용하면 조회 실패 상황을 예외로 자연스럽게 표현할 수 있다.
- MemberNotFoundException, InvalidMemberRequestException 같은 사용자 정의 예외는 에러 상황의 의미를 더 분명하게 만들어준다.
'TIL > [TIL]' 카테고리의 다른 글
| [TIL]Spring Boot 인터셉터 등록, REST API 응답 설계, Validation, Swagger (0) | 2026.06.02 |
|---|---|
| [TIL]Spring Boot 파일 업로드, 전역 예외 처리, 인터셉터 (0) | 2026.06.01 |
| [TIL]Spring Boot 요청 매핑, 핸들러 메서드, 세션 처리, 그리고 AOP (0) | 2026.05.28 |
| [TIL]Spring Bean, DI, Life Cycle, Scope, 외부 설정값, AOP 정리 (0) | 2026.05.27 |
| [TIL]Spring Core: DI, Spring Container, Bean, 계층 구조, @Autowired와 컴포넌트 스캔 (0) | 2026.05.26 |