@Data
@RequestBody
@ResponseBody
엔티티 바인딩 방식
public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member) {
Long id = memberService.join(member);
return new CreateMemberResponse(id);
}
@Data
static class CreateMemberRequest { // DTO
private String name;
private Address address;
}
@PostMapping("/api/v2/members")
public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request) {
Member member = new Member();
member.setName(request.getName());
member.setAddress(request.getAddress());
Long id = memberService.join(member);
return new CreateMemberResponse(id);
}
"defaultMessage": "반드시 값이 존재하고 길이 혹은 크기가 0보다 커야 합니다.",
"objectName": "member",
"field": "name",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotEmpty"
public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request) {
Member member = new Member();
member.setName(request.getName());
member.setAddress(request.getAddress());
임베딩 데이터인 Address도 받아올 수 있게 구성
{
"name": "userA",
"address": {
"street":"seoul",
"city":"seoul",
"zipcode":"123-45"
}
}
JSON 계층을 구성하고 전송하면 잘 들어간다.
insert into member (city, street, zipcode, name, member_id) values (?, ?, ?, ?, ?)
insert into member (city, street, zipcode, name, member_id) values ('seoul', 'seoul', '123-45', 'userA', 1);
ddl-auto : none 시 테이블을 못찾는 문제
creater가 아니라 기본적으로 테이블을 만들어주지 않아서 발생하는 것 같은데
따로 스크립트로 만들어줘야 하는듯?
@GetMapping("/api/v1/members")
public List<Member> membersV1() {
return memberService.findMembers();
}
이렇게 엔티티 자체를 List로 반환 시 아주 쉽게 회원 정보를 보낼 수 있지만
[
{
"id": 1,
"name": "userA",
"address": {
"city": "seoul",
"street": "11111",
"zipcode": "123-45"
},
"orders": []
},
{
"id": 2,
"name": "userB",
"address": {
"city": "seoul",
"street": "11111",
"zipcode": "123-45"
},
"orders": []
}
]
불필요한 Order라는 엔티티 정보까지 노출이 되고, @JsonIgnore를 통해서 숨기더라도
엔티티에 뷰 계층의 로직이 추가되는 양방향 연관 관계가 생겨버리는 문제점이 있다.
DTO
@Data
@AllArgsConstructor
private static class Result<T> {
private T data;
}
@Data
@AllArgsConstructor
private static class MemberDto {
private String name;
private Address address;
}
DTO와 DTO가 받을 데이터 감싸는 클래스를 만든다.
@GetMapping("api/v2/members")
public Result<List<MemberDto>> memberV2() {
List<Member> findMembers = memberService.findMembers();
List<MemberDto> collect = findMembers.stream()
.map(m -> new MemberDto(m.getName(), m.getAddress()))
.collect(Collectors.toList());
return new Result<>(collect);
}
이렇게 되면 엔티티에서 데이터는 데이터대로 가져온 뒤 그 데이터를 DTO를 통해 랩핑하기 때문에 엔티티도 노출되지 않고 Data라는 타입으로 계층 구조를 생성할 수 있다.
{
"새로운 데이터" : "새 데이터",
"data": [ // Wrapped
{
"name": "userA",
"address": {
"city": "seoul",
"street": "11111",
"zipcode": "123-45"
}
}
]
}
기존과 다르게 data라는 이름으로 계층이 분리되었기 때문에 엔티티가 수정되어 새 데이터가 생기더라도 API 스펙 자체는 변하지 않는다.
그리고 엔티티가 수정 되더라도 컴파일 시점에서 모든 에러가 잡히기때문에 수정이 용이하고
엔티티와 DTO가 1:1 연관 관계를 가진다.
@Data
static class CreateMemberRequest { // 반드시 있어야 함
private String name;
}
@Data
static class CreateMemberResponse { // 없어도 됨
private Long id;
public CreateMemberResponse(Long id) {
this.id = id;
}
}