[REST API] 4. 주소 제거

문정준's avatar
May 08, 2025
[REST API] 4. 주소 제거

📌 REST API 구조 전환 핵심 요약

항목
변경 전
변경 후
URL 스타일
/board/save, /reply/{id}/delete 등 동사 포함 주소
/board, /reply/{id} 등 RESTful 주소
요청 타입
application/x-www-form-urlencoded
application/json
HTTP 메서드
GET, POST만 사용
GET, POST, PUT, DELETE 구분
데이터 전달 방식
request.setAttribute()로 모델 전달
DTO 객체를 JSON으로 직렬화
응답 구조
뷰 리턴 (redirect:)
@ResponseBody + Resp<?> JSON 구조
엔티티 직렬화
Lazy 로딩 충돌 발생
DTO로 변환 후 안전 직렬화

 

✅ 주요 변화 상세 정리

1. 주소에서 동사 제거 + HTTP 메서드 분리

  • 기존에는 URL 자체가 기능을 표현함 (/reply/save, /reply/{id}/delete)
  • → REST API에서는 자원(명사)을 주소로, 동작은 HTTP 메서드로
기능
변경 전
변경 후
댓글 저장
POST /reply/save
POST /reply
댓글 삭제
POST /reply/{id}/delete
DELETE /reply/{id}
게시글 저장
POST /board/save
POST /board
게시글 목록
GET /board/list (뷰 반환)
GET /board/list (JSON 응답)
게시글 상세
GET /board/{id} (뷰 반환)
GET /board/{id}/detail (JSON 응답)
회원 가입
POST /join (뷰 or redirect)
POST /join (JSON 응답)
회원 수정
POST /user/update
PUT /user
뷰 모델 세팅
request.setAttribute("model", dto)
❌ 사용 안 함 (클라이언트 렌더링 or API 전용)
 

✅ 기능별 구조 전환

1. 글 목록 보기 (GET)

@GetMapping("/board/list") public @ResponseBody Resp<?> getList(@RequestParam(defaultValue = "") String keyword, ...) { BoardResponse.ListDTO listDTO = boardService.글목록보기(keyword, ...); return Resp.ok(listDTO); }
  • /board/list로 조회
  • Entity → DTO 변환 후 응답
  • Lazy 로딩 방지 위해 내부에서 .map(BoardResponse.DTO::new) 사용
📦 JSON 응답 예시:
{ "status": 200, "msg": "성공", "body": { "boards": [ { "id": 20, "title": "제목20", "content": "내용20", "isPublic": true, "userId": 1, "createdAt": "2025-05-07 11:27:51.027589" }, { "id": 19, "title": "제목19", "content": "내용19", "isPublic": true, "userId": 1, "createdAt": "2025-05-07 11:27:51.02659" }, { "id": 18, "title": "제목18", "content": "내용18", "isPublic": true, "userId": 1, "createdAt": "2025-05-07 11:27:51.02659" } ], "prev": -1, "next": 1, "current": null, "size": 3, "totalCount": 19, "totalPage": 7, "isFirst": true, "isLast": false, "numbers": [ 0, 1, 2, 3, 4 ], "keyword": "" } }

2. 글 보기(업데이트 글보기) (GET)

@GetMapping("/board/{id}") public @ResponseBody Resp<?> getBoardOne(@PathVariable("id") int id) { User sessionUser = (User) session.getAttribute("sessionUser"); BoardResponse.DTO respDTO = boardService.글보기(id, sessionUser.getId()); return Resp.ok(respDTO); }
  • 로그인한 유저의 ID를 기반으로 게시글을 조회함
  • 예: 본인이 쓴 글인 경우, 수정/삭제 버튼 활성화 등을 컨트롤 가능
  • JSON으로 응답 → 프론트엔드 분리 구조에 적합
  • @ResponseBody + Resp.ok() 조합으로 표준화된 응답 구조 사용
📦 JSON 응답 예시:
{ "status": 200, "msg": "성공", "body": { "id": 4, "title": "제목4", "content": "내용4", "isPublic": true, "userId": 3, "createdAt": "2025-05-07 10:34:26.612766" } }

 

3. 글 상세 보기 (GET)

@GetMapping("/board/{id}/detail") public @ResponseBody Resp<?> getBoardDetail(@PathVariable Integer id) { BoardResponse.DetailDTO detailDTO = boardService.글상세보기(id); return Resp.ok(detailDTO); }
  • /board/{id}/detail로 API 요청
  • 기존 request.setAttribute() 제거
  • 댓글 리스트까지 포함해 JSON으로 응답
📦 JSON 응답 예시:
{ "status": 200, "msg": "성공", "body": { "id": 4, "title": "제목4", "content": "내용4", "isPublic": true, "isOwner": true, "isLove": false, "loveCount": 2, "username": "love", "createdAt": "2025-05-07T01:34:26.612+00:00", "loveId": null, "replies": [ { "id": 3, "content": "댓글3", "username": "ssar", "isOwner": false }, { "id": 2, "content": "댓글2", "username": "cos", "isOwner": false }, { "id": 1, "content": "댓글1", "username": "ssar", "isOwner": false } ] } }

4. 글 쓰기 (POST)

@PostMapping("/board") public @ResponseBody Resp<?> save(@Valid @RequestBody BoardRequest.SaveDTO reqDTO, Errors errors) { User sessionUser = (User) session.getAttribute("sessionUser"); BoardResponse.DTO respDTO = boardService.글쓰기(reqDTO, sessionUser); return Resp.ok(respDTO); }
  • @RequestBody를 통해 JSON으로 데이터 받음
  • 뷰 리턴 대신 DTO로 응답

5. 글 수정 (PUT)

@PutMapping("/board/{id}") public @ResponseBody Resp<?> update(@PathVariable Integer id, @Valid @RequestBody BoardRequest.UpdateDTO updateDTO, Errors errors) { User sessionUser = (User) session.getAttribute("sessionUser"); BoardResponse.DTO respDTO = boardService.글수정(id, updateDTO, sessionUser); return Resp.ok(respDTO); }
  • HTTP 메서드 PUT 사용으로 의미 명확화
  • JSON 기반 DTO 전달

6. 댓글 쓰기 (POST)

@PostMapping("/reply") public @ResponseBody Resp<?> save(@Valid @RequestBody ReplyRequest.SaveDTO reqDTO, Errors errors) { User sessionUser = (User) session.getAttribute("sessionUser"); ReplyResponse.DTO respDTO = replyService.댓글쓰기(reqDTO, sessionUser); return Resp.ok(respDTO); }

7. 댓글 삭제 (DELETE)

@DeleteMapping("/reply/{id}") public @ResponseBody Resp<?> delete(@PathVariable Integer id) { User sessionUser = (User) session.getAttribute("sessionUser"); replyService.댓글삭제(id, sessionUser.getId()); return Resp.ok(null); }
  • DELETE 메서드 사용으로 URL에 동사 제거
  • 댓글 삭제 시에도 JSON 응답 유지
📦 JSON 응답 예시:
{ "status": 200, "msg": "성공", "body": null }

8. 회원가입 (POST)

@PostMapping("/join") public @ResponseBody Resp<?> join(@Valid @RequestBody UserRequest.JoinDTO reqDTO, Errors errors) { UserResponse.DTO respDTO = userService.회원가입(reqDTO); return Resp.ok(respDTO); }
  • 기존 @ModelAttribute 방식 제거
  • JSON 기반 요청으로 전환

9. 회원정보 수정 (PUT)

@PutMapping("/user") public @ResponseBody Resp<?> update(@Valid @RequestBody UserRequest.UpdateDTO updateDTO, Errors errors) { User sessionUser = (User) session.getAttribute("sessionUser"); User userPS = userService.회원정보수정(updateDTO, sessionUser.getId()); session.setAttribute("sessionUser", userPS); return Resp.ok(null); }
  • PUT 메서드로 REST 설계에 부합
  • 수정 후 세션 갱신 처리

🧠 기타 정리: ByteBuddyInterceptor 관련

Lazy 로딩 중 JSON 직렬화 과정에서 연관 객체를 접근하면 발생하는 예외
  • com.fasterxml.jackson.databind.JsonMappingException: ByteBuddyInterceptor 예외 발생
  • 이유: 엔티티를 JSON으로 직렬화할 때 연관 객체에 접근 → 세션 종료로 인한 Lazy 로딩 실패
해결
→ 반드시 Entity가 아닌 DTO로 변환해서 JSON 직렬화할 것
this.boards = boards.stream() .map(board -> new DTO(board)) .toList();
 
Share article

sxias