개괄
이번 개인 과제는 JPA를 사용해서 게시판을 만들어보는 과정이었다. 무슨 문제가 있었는지 확인해보고, 해결해보자.
사실은 게시판이라기보다는 거의 뭐 쪽지 짬통 테스트같은 느낌이다.
github
https://github.com/choincnp/SpringBoard
설계 방식
1. 요구사항 받아들이기
2. API 명세서 설계하기
3. 기능명세서 설계하기
4. 디렉토리 분리하기
개발 flow
1. entity를 먼저 만든다.
2. 그 entity를 담을 Repository를 만든다.
3. 2번에서 만든 Repository를 Controll할 Controller를 만든다.
4. Controller에서 받을 RequestDto를 만들기
5. 이제 받을 RequestDto를 핵심 로직으로 조질 Service를 설계한다.
6. 핵심 로직을 거쳐서 return해줄 ResponseDto를 설계한다.
여기서 문제 : 왜 Entity를 Dto에 담아서 return해줘야 하는가?
간단히 말하면, Entity의 모든 정보를 담아서 return해줄 필요는 없기 때문이다.
Board의 Entity 안에는 많은 정보가 들어간다. PK인 id값을 포함해 제목, 내용, 작성자 등도 들어가지만, 가장 중요한 게시물을 지울 수 있는 password값이 들어가있다. password값까지 response로 리턴해줄 필요도 없고, 또한 누가 접근했을 때 한단계 더 숨겨줄 수 있기 때문이다.(Encapsulism과 비슷한 맥락이라고 보면 된다.)
이 Letter를 list로 만들어서 반환하는 것이 아니라,
이렇게 ResponseDto에 담아 주는것이 현명하다.
그럼 Letter를 ResponseDto로 바꿔 주어야 하는데, 어떻게, 또 어느 Layer에서 변환을 해 주어야 할까?
Controller Layer에서 변환해주어도 괜찮지만, 보통은 Service Layer에서 변환작업을 해 주는게 좋기도 하고, flow에 맞다.
그럼 Entity를 DTO로 어떻게 바꿔줄까?
내가 알아본 방법은 두 가지였다.
1. Stream을 이용해 바꿔주기
2. Array로 만들고 List로 바꿔주기
그 중 Stream을 이용하기로 했다.
이렇게 자바8의 collection을 사용해서 바꾸어 주었다.
LetterController
@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class MainController {
private final LetterService letterService;
@GetMapping("/posts")
public List<LetterResponseDto> viewAll(){
return letterService.findAll();
}
@PostMapping("/post")
public Long postLetter(@RequestBody LetterRequestDto letterRequestDto){
return letterService.postLetter(letterRequestDto);
}
}
여기서 @RestController란 무엇일까?
@RestController = @Controller + @ResponseBody이다.
@Controller 어노테이션은 기존의 컨트롤러 어노테이션과 똑같고,
@ResponseBody 어노테이션은 return해주는 객체를 MessageConverter를 통해 매핑해서 본문에 담아 클라이언트에 전달한다.
처음에는 RequestBody와 헷갈려서 그대로 전달했다가 에러가 나버렸다.
그래서 Controller에 @RequestBody를 다 붙여주었다.
그랬더니 또 문제가 났다.
api 명세서에는 password만 들고 들어와야 하는 것이다.
보통 우리가 게시판에서 글을 지우려면 pop-up창에 password 하나만 치면 삭제가 되는 구조를 많이 이용한다.
그래서 이 password값만 받아오고 싶은데, 도저히 RequestDto에 있는 password값을 가져와서 다시 확인하기는 싫었다.
나중에 상세 페이지에 들어가서 삭제를 누르려면 RequestDto를 이용하면 되겠지만, 상세 페이지 밖에서 글을 삭제하려면
RequestDto에 있는 타이틀, 제목같은 not null값을 다 채워줘야 하기 때문이다.
한참을 500에러와 씨름하다가 다음과 같은 깨달음을 얻었다.
@RequestBody
RequestBody는 요청값을 json type로 받겠다고 선언하는 어노테이션으로, 이 어노테이션 덕분에 우리는 DTO 형태로 값을 가져올 수 있다. Spring은 Controller로 요청을 받을 때 jackson이라는 내장 라이브러리에 있는 MappingJackon2HttpMessageConverter를 이용해 요청값으로 들어온 json형태의 데이터를 DTO에 맞는 형태로 파싱해주게 된다. 그래서 우리는 스프링을 통해 json 형태를 편리하게 DTO라는 객체로 받아올 수 있는 것이다.
그래서 이 JSON 형태의 글이 @Responsebody 어노테이션을 통해
RequestDto requestDto = new RequestDto(title,content,author,password)
형태로 자동 바인딩되어 저장된다.
이를 "역직렬화 / DeSerialization"이라고 부른다! (Singleton패턴에서 왜 이런 개사기스킬을 만들어놨는지 궁금했는데 여기서 깨달았다.)
역직렬화는 생성자를 거치지 않고, Java의 Reflection을 통해 객체를 정의하는 매커니즘이다. 직렬화 가능한 클래스들은 기본 생성자가 항상 있어야 하기 때문에 RequestDto에 기본 생성자가 정의되어있지 않으면 데이터 바인딩에 실패한다.
아무튼 나는
@DeleteMapping("/post/{id}") //RequestBody 방식으로 구현, body : raw - text
public String deleteLetter(@PathVariable Long id, @RequestBody String password) throws JSONException {
return letterService.deleteLetter(id, password);
}
이렇게 RequestBody 어노테이션 뒤에 String password를 선언하였기 때문에 JSON타입의 password가 내가 생각한 JSON객체로 들어오지않고
String password = “{\“password\“:\“1234\“}”;
와 같은 형태로 들어왔던 것이다.
그럼 어떻게 하는데!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
방법은 3개가 있다.
1. 죽어도 RequestBody를 쓰고 싶으면 @RequestBody JSONObject password로 받기
2. @ModelAttribute를 쓰기 : ModelAttribute는 RequestBody나 RequestParam을 선언하지 않으면 디폴트로 들어가기 때문에
@DeleteMapping("/post/{id}") //ModelAttribute 방식으로 구현, body : x-www.form-urlencoded
public String deleteLetter(@PathVariable Long id, String password) throws JSONException {
return letterService.deleteLetter(id, password);
}
이렇게 선언해서 나중에 body타입으로 받아주기
3. @RequestParam 사용하기 : RequestParam은 1개의 parameter를 받기 위해 사용되고있다. null 들어가면 안된다.
@DeleteMapping("/post/{id}") //REQUESTPARAM 방식으로 구현, body : form-data
public String deleteLetter(@PathVariable Long id, @RequestParam String password, Model m) throws JSONException {
m.addAttribute("password",password);
return letterService.deleteLetter(id, password);
}
느낀점
정말 맛있는 과제였다. 덕분에 정말 HTTP request, response, 그리고 MVC패턴과 각종 어노테이션에 대해서 학습할수 있는 좋은 기회였다....
앞으로 남은 과제는 유효성 처리를 어느 Layer에서 할까에 대해 고민해보아야겠다.
https://velog.io/@jsb100800/Spring-boot-DTO-Entity-간-변환-어느-Layer에서-하는게-좋을까
https://thalals.tistory.com/230
https://frogand.tistory.com/163
https://mangkyu.tistory.com/72
'웹개발 > 항해' 카테고리의 다른 글
항해 4주차, - Week I learned (0) | 2023.02.12 |
---|---|
TIL - 팩토리 메서드 디자인 패턴과 함께하는 버스 공장 만들어보기 (0) | 2023.02.06 |
항해 3주차, 객체지향의 길목에서 - Week I learned (0) | 2023.02.05 |
TIL - RestTemplate로 유튜브에서 검색결과를 받자 (0) | 2023.02.02 |
항해 2주차, 객체지향으로 가는 길 - Week I learned (0) | 2023.01.30 |