Html에서 Post로 보낸 데이터가 Null이 뜬다..
수업을 따라가며 작성했던 프로젝트를 복습하는데 HTML에서 보낸 데이터가 Null이 뜨며 DB에 Null로 저장되는 상황이 발생하였다.
2022-11-10 00:25:31.694 INFO 7484 --- [nio-8088-exec-2] c.m.bbs.controller.ArticleController : title = null
2022-11-10 00:25:31.694 INFO 7484 --- [nio-8088-exec-2] c.m.bbs.controller.ArticleController : content = null
Hibernate: insert into article (content, title) values (?, ?)
2022-11-10 00:25:31.776 INFO 7484 --- [nio-8088-exec-2] c.m.bbs.controller.ArticleController : generatedId:1
복습하려고 만든 프로젝트가 2개, 진도를 따라가려는 프로젝트가 1개 총 3개 였는데, 처음 발견된 오류였다.
일단, API가 작동되면서 DB에 이상 없이 저장이 된다는 점
문제는 Dto와 바인딩이 안된다는 점을 보고 이상한 점을 계속 찾아봤지만
타 프로젝트와 다른 코드가 없어서 더 미궁속으로 빠졌던 것 같다.
HTML 코드
{{>layouts/header}}
<form class="container" action = "/articles/new" method = "post">
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control" id = "title" name = "title"/>
<div id="emailHelp" class="form-text">제목을 입력해주세요.</div>
<label for="content" class="form-label">Content</label><br>
<textarea id="content" name = "content"></textarea>
<div id="emailHelp" class="form-text">내용을 입력해주세요.</div>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
{{>layouts/footer}}
DTO 코드
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ArticleDto {
private Long id;
private String title;
private String content;
public ArticleDto(String title, String content) {
this.title = title;
this.content = content;
}
public Article toEntity(){
return new Article(this.id, this.title, this.content);
}
}
Controller 코드
@PostMapping("/new")
public String createArticle(ArticleDto articleDto) {
Article savedArticle = articleRepository.save(articleDto.toEntity());
return String.format("redirect:/articles/%d", savedArticle.getId());
}
주의 깊게 본 점
- html에 태그 안에 있는 name이 dto의 변수명과 다른가.
- 다른 오타는 존재하지 않는가.
해결
이런 저런 글들을 찾아보면서 @Setter가 있으면 해결된다는 글을 보고
@lombok의 @Setter 어노테이션을 추가하여 해결할 수 있었다.
하지만, 무분별한 @Setter의 사용은 좋지 않다고 배웠고 원인을 찾을 수 없었기에 다른 글을 더 찾게 되었다.
@ModelAttribute에 대한 설명중에서 문제의 원인을 찾을 수 있었다.
Spring은 URL 파라미터 또는 POST Form Data형태의 파라미터를 DTO와 같은 특정 클래스와 자동으로 바인딩을 진행하는데, 이때 반환되는 객체를 커맨드 객체라고 부른다고 합니다. 그리고 커맨드 객체를 구성하기 위한 매핑 규칙은 다음과 같습니다.
- NoArgsConstructor과 AllArgsConstructor 둘 다 있는 경우
- NoArgsConstructor 호출하고, setter 호출하여 param을 필드에 각각 초기화한다.
- AllArgsConstructor만 있는 경우
- AllArgsConstructor 호출하여 param을 필드에 각각 초기화한뒤, setter 호출하여 param을 필드에 각각 다시 초기화하여 덮어 씌운다.
참고한 블로그와 마찬가지로 저 역시 1번에 해당되었고, setter가 존재하지 않아 param들을 필드에 초기화 할 수 없었던 것입니다.
@Setter와 함께 @AllArgsConstructor, @NoArgsConstructor을 모두 제거하니 바인딩이 잘 되는 것을 확인할 수 있었습니다.
@NoArgsConstructor와 @Setter를 조합하여 사용할 수 있지만 클래스의 데이터는 접근하는 범위를 제한하면서 사용하는 것이 좋으므로 되도록 Setter를 사용하지 않고, 생성자를 통해 파라미터를 받기 위해 public 생성자 중 제일 인자가 적은 생성자를 기준으로, 인자의 이름이 파라미터 이름과 동일하게 생성하여 사용하는게 좋을 것 같습니다.
매개변수에 @ModelAttribute를 사용하지 않아도 바인딩 되는 것 또한 궁금하였는데,
Controller에 @ModelAttribute를 붙이지 않아도 자동으로 커맨그 객체를 구성한다고 합니다. 스프링에서 int나 String 같은 단순 자료형은 @RequestParam으로 인식하고, 복잡한 객체는 @ModelAttritube로 인식한다고 합니다.
하지만 간단한 숫자나 문자로된 요청도 복잡한 객체로 인식할 수 있으니 어노테이션을 붙여주는게 좋다고 합니다.
참조