연관관계 적용3(궁금증 해결하기)
연관관계 목차
문제
Registry(게시글)와 Comment(댓글)의 일대다 연관관계 매핑을 완료했다.
연관관계가 맺어졌기 때문에 Comment는 Registry 객체를 통해 게시글 정보를 참조할 수 있고
굳이 외래키 값을 직접 들고 있을 필요가 없어졌다.
이 과정에서 댓글 등록 로직을 수정하던 중 특정 방식이 왜 동작하는지 궁금증이 생겨 정리해보았다.
의문점 : postComment
기존에는 댓글을 등록할 때 JS에서 다음과 같이 form 데이터를 보냈다.
1
2
3
4
5
let form_data = new FormData()
form_data.append("comment", $("#comment").val())
form_data.append("nickname", nickname)
form_data.append("registryId", $("#RegistryId").html())
form_data.append("registryNickname", $("#user").text())
연관관계 매핑 이후에는 다음과 같이 수정했다.
1
2
3
4
let form_data = new FormData()
form_data.append("comment", $("#comment").val())
form_data.append("nickname", nickname)
form_data.append("registry", $("#RegistryId").html())
게시글의 id 값을 registryId로 보내던 것을 registry라는 이름으로 보내도록 변경했다.
DTO 수정
DTO에서도 기존의 id 필드 대신 Registry 객체를 직접 받도록 수정했다.
1
2
3
4
5
public class CommentDto {
private String nickname;
private String comment;
private Registry registry;
}
Comment 엔티티는 DTO를 받아 다음과 같이 생성된다.
1
2
3
4
5
public Comment(CommentDto commentDto) {
this.nickname = commentDto.getNickname();
this.comment = commentDto.getComment();
this.registry = commentDto.getRegistry();
}
front에서는 숫자 형태의 id 값만 보냈는데 서버에서는 Registry 객체로 정상 바인딩이 되었다.
여기서 의문이 생겼다.
숫자 하나만 보냈는데 어떻게 Registry 객체가 만들어졌을까
그리고 어떻게 게시글의 pk라는 것을 알았을까
Why?
Spring MVC 요청 처리 과정에서 요청 파라미터가 숫자 하나이고 DTO 또는 엔티티 필드 타입이 객체인 경우
Spring은 해당 객체를 직접 조회하지 않고
기본 생성자를 통해 객체를 생성한 뒤 요청 파라미터 이름과 일치하는 필드에 해당 숫자를 세팅하려고 시도한다.
이후 해당 객체가 JPA 영속성 컨텍스트에 전달되면
JPA는 해당 객체의 식별자 값을 기준으로 연관 엔티티의 참조로 인식하고
필요한 시점에 실제 DB 조회를 수행한다.
이 때문에 id 값만 전달했음에도 연관 객체가 이미 로딩된 것처럼 동작하는 것처럼 보이게 된다.
Test
front
1
2
3
4
5
6
7
8
let form_data = new FormData()
form_data.append("comment", $("#comment").val())
form_data.append("nickname", nickname)
form_data.append("registry", $("#RegistryId").html())
for (let key of form_data.keys()) {
console.log(key, ":", form_data.get(key));
}
front에서 3개의 데이터를 보낸다.
CommentController에서 받는 값을 출력시켜봤다.
front에서 id값만 보냈는데 dto에서는 객체로 보낸다.
Postman으로 실행하기
DTO에 Entity를 넣는 것은 권장되지 않는다
DTO에 Entity를 넣을 수는 있지만 좋은 선택은 아니다.
1. 역할 분리가 깨진다.
Entity는 데이터베이스와 직접 연결된 객체이고 DTO는 계층 간 데이터를 전달하기 위한 객체다.
Entity를 그대로 사용하면 불필요한 데이터나 노출되면 안 되는 정보까지 함께 전달될 위험이 있다.
2. 생명주기가 다르다.
DTO는 요청과 응답에서 잠깐 사용되는 일회성 객체이고 Entity는 JPA가 관리하는 영속 객체다.
id만 전달하기
그래서 DTO에서 Entity를 직접 받지 않고 연관 객체의 id 값만 받는 방식을 사용한다.
JS에서는 다음과 같이 게시글 id만 전달한다.
1
2
3
4
let form_data = new FormData()
form_data.append("comment", $("#comment").val())
form_data.append("nickname", nickname)
form_data.append("registryIdx", $("#RegistryId").html())
DTO에서도 id 값만 받는다.
1
2
3
4
5
public class CommentDto {
private String nickname;
private String comment;
private Long registryIdx;
}
Service 계층에서 id를 이용해 Registry를 조회한 뒤 Comment에 연관관계를 설정한다.
findById 대신 getReferenceById
Registry를 조회할 때 findById 대신 getReferenceById를 사용할 수도 있다.
getReferenceById는 실제 엔티티 대신 프록시 객체를 반환한다.
이 프록시는 id 외의 필드에 접근하는 순간 SELECT 쿼리가 실행된다.
1
2
3
4
5
6
7
Registry registry = registryRepository.getReferenceById(commentDto.getRegistryIdx());
Comment comment = Comment.builder()
.comment(commentDto.getComment())
.nickname(commentDto.getNickname())
.registry(registry)
.build();
registry의 id값으로 보내기
js에서 registry의 id값으로 명시해서 보냈다.
1
2
3
4
5
// js
let form_data = newFormData()
form_data.append("comment", $("#comment").val())
form_data.append("nickname", nickname)
form_data.append("registry.idx",$("#RegistryId").html())
controller
1
2
3
4
@PostMapping("/comment")
public Comment setComment(@ModelAttribute CommentDto commentDto) {
return commentService.setComment(commentDto);
}
1
2
3
4
// Registry.java
public void setIdx(Long idx) {
this.idx = idx;
}
registry.idx로 보냈을 때 성공하려면 id에 대한 setter가 있어야 하며
registry.idx가 아닌 registry 자체로 값을 보낼 경우
Spring은 기본 생성자를 통해 객체를 생성하고 해당 객체를 바인딩 대상으로 사용한다.
id값은 생성자 작성 안해도 된다.(id는 자동으로 생성되는 auto_increment 쓰므로)
test
idx와 nickname에서는 setter가 맞는지 확인해본다.
setter를 작성하고 test를 했더니 아래와 같이 나왔다.
1
2
3
4
5
6
7
public void setIdx(Long idx) {
this.idx = idx;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
1
2
3
4
5
6
//js
let form_data = newFormData()
form_data.append("comment", $("#comment").val())
form_data.append("nickname", nickname)
form_data.append("registry.idx",$("#RegistryId").html())
form_data.append("registry.nickname",$("#user").text())
post는 되었지만 registry 객체에 title, main이 null로 뜨는 것을 볼 수 있다.
setter로 id, nickname 값만 넣어서 나머지 필드는 null로 뜨는 것이다.
registry.~~는 setter로 동작해서 값을 넣어준다.라는 것을 알게 되었다.
만약 idx에 대해서만 setter를 설정하면 js에서 idx값과 nickname을 전달해도 idx 값만 받아온다.
마무리
이번 과정을 통해 댓글을 등록하는 방식에는 두 가지가 있다는 것을 알게 되었다.
하나는 객체 형태로 바인딩되는 방식 다른 하나는 id만 전달해서 서버에서 조회하는 방식이다.
또한 단순히 findById만 사용하는 것이 아니라 프록시를 활용하는 getReferenceById라는 선택지도 알게 되었다.
reference





