레시피 업로드 기능을 이용하면서, Recipe
라는 엔티티에 Value 타입으로 RecipeInfo
, RecipeStatistics
를 두고 싶었다. Entity 객체 안에 또다른 Reference type의 필드를 두고자 한다는 소리이다.
RecipeInfo
는 레시피에 대한 설명, RecipeStatics
은 레시피의 통계 관련한 필드(조회수, 평점, 북마크 개수)를 저장할 수 있도록 했다. 각각의 클래스 코드를 까보면 다음과 같다.
@Embeddable
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RecipeInfo {
private String description;
private String cookingTime;
@Enumerated(EnumType.STRING)
private Difficulty difficulty;
@Enumerated(EnumType.STRING)
private Serving serving;
}
@Embeddable
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RecipeStatistics {
// 평점 : 실제로는 10.0으로 나눈 값이 쓰임
private int ratings;
private int viewCount;
private int bookMarkCount;
}
RecipeInfo
의 enum타입은 더욱 별 거 없다.
@RequiredArgsConstructor
@Getter
public enum Difficulty {
EASY("쉬움"),
NORMAL("보통"),
HARD("어려움"),
UNSELECTED("선택안함");
private final String description;
}
@Getter
@RequiredArgsConstructor
public enum Serving {
ONE("1인분"),
TWO("2인분"),
THREE("3인분"),
FOUR("4인분"),
MORE("5인분"),
UNSELECTED("선택안함");
private final String description;
}
위 밸류 객체들을 DTO로 어떻게 받아야할까? 그 전에 받아지긴 할까?
궁금했기 때문에, 바로 DTO 클래스를 만들어 실험해봤당.
public class CreateRecipeDto {
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public static class Request {
@NotNull
@NotBlank
private String title;
private String description = "";
private CookingTime cookingTime = CookingTime.UNDEFINED;
private Difficulty difficulty = Difficulty.UNDEFINED;
private Serving serving = Serving.UNDEFINED;
private List<CreateStepDto.Request> steps = new ArrayList<>();
}
Reference 타입의 데이터들에 대해 ""
, CookingTime.UNDEFINED
등의 기본 객체를 저런 식으로 할당하면 Builder나 생성자에 null
값이 들어와도 할당된 기본 객체가 바인딩된다. @ToString
을 통해 DTO 바인딩 상태도 살펴볼 것이다!
이제 @RequestBody
가 handler argument에 붙은 Controller 메소드를 살펴보자!
@RestController
@RequestMapping("/recipe")
@RequiredArgsConstructor
@Slf4j
public class RecipeController {
private final RecipeService recipeService;
@PostMapping
public ResponseEntity<CreateRecipeDto.Request> postRecipe(
@RequestBody @Valid CreateRecipeDto.Request request) {
log.info("CreateRecipeDto : {}", request);
return ResponseEntity.ok(request);
}
}
그냥 @Slf4j
의 log.info
를 이용해 input request dto를 로그로 남기는 작업과, Enum이 다시 JSON 응답으로 deserialize되는지까지를 확인하기 위해 ok()
안의 body까지 저렇게 담았다.ㅎㅎ
POSTMAN으로 위와 같은 JSON body를
/recipe
URL로 전송해보자. difficulty
는 Difficulty.NORMAL
, serving
은 Serving.TWO
enum 객체가 매핑되길 기대하는 상황이다. coockingTime
필드는 보내지 않은 상태다. null
값으로 보내졌다고 생각해도 무방하다. 이는 앞서 설정했던, enum을 포함한 reference type의 초기화가 잘 이뤄졌는지 확인하기 위함이다.
이제 응답으로 온 JSON body를 보자! 두근두근
Request
의 모든 필드를 보내지 않았음에도 불구하고("steps"
와 "cookingTime"
), 해당 필드가 존재한다. 심지어 cookingTime
의 경우 우리가 최초로 설정했던 CookingTime.UNDEFINED
가 할당된 것을 볼 수 있다. 그리고 정상적으로 넘겨준 "NORMAL"
, "TWO"
에 대해서도 오류 없이 응답이 잘 deserialized된 것을 보면 문자열이 enum type에 잘 바인딩되었다고 예상할 수 있다!
이제 콘솔창에 찍혔을 로그를 확인하자.
JSON 응답을 확인했으면 더 볼 필요도 없는 내용이긴 하지만(..) 로그도 잘 찍혔다.
별개로 한 건 완전 많은데(프로젝트 코드 작성, 관련 레퍼런스 공부, JPA 공부..) 프로젝트 코드랑 엮어서 블로그 정리하려고 하니 시간이 너무 부족하다. 일단 오늘 멘토링 하면서 함께 토론했던 내용들 캡처를 보면서 회고해보자..
다대다 연관관계를 사용할 때 제3의 연관테이블을 작성하게 되는데,
@ManyToMany
를 쓰지 않냐고 여쭤보았다. 확장성이 떨어지기도 하고, 연관 테이블 자체에 필요한 필드 추가가 까다롭다는 점을 얘기해주셨다. 사실 메인은 성능이라고..
그리고 한쪽 엔티티에서 연관관계를 가진 다른 엔티티를 불러와야할 상황이 있을 땐 결국 양방향으로 연관관계를 매핑하는 것이 좋을 것이라고 말씀도 들었다.
또! 개인적으로 고민했던것. 프로젝트 기능 구현만 한다고 하면 크게 오래걸리지 않는데, 주변적인 것에 시간을 쏟는게 좀 걱정이 되서 말씀을 드렸다. 그냥 기능 구현이나 빨리 하겠다고 일갈했더니 그러라고 하셨다(?).