1. 스프링 MVC의 기본 원칙 메서드 파라미터에 사용자 정의 객체(예: QuestionDto)를 선언하면, 스프링은 내부적으로 이를 @ModelAttribute로 취급합니다.
- 요청이 오면 QuestionDto 객체를 자동 생성합니다.
- 생성된 객체를 자동으로 Model에 추가합니다.
- 이때 모델에 등록되는 이름(Key)은 클래스명의 첫 글자를 소문자로 바꾼 questionDto가 됩니다.
즉, 아래 두 코드는 완전히 동일하게 동작합니다.
// 1. Model을 명시적으로 적은 경우 public String modify(QuestionDto questionDto, Model model) { model.addAttribute("questionDto", questionDto); // 사실상 중복 처리 return "question/inputForm"; }
// 2. Model 생략 (스프링이 자동으로 Model에 담아줌) public String modify(QuestionDto questionDto) { return "question/inputForm"; }
2. 왜 오류가 나거나 데이터가 전달 안 되는 걸까? (원인 분석) 자동으로 모델에 담기는데도 오류가 나거나 값이 비어있는 이유는 '객체의 생성 방식'과 'HTML 템플릿(Thymeleaf)에서 찾는 이름(Key)' 때문입니다.
① 원인 1: HTML(Thymeleaf)에서 바인딩하는 이름(Key) 불일치 스프링이 모델에 자동으로 담을 때 사용하는 Key 이름은 클래스 이름 기준(카멜 케이스)입니다.
- QuestionDto → Model Key: questionDto
- QuestionForm → Model Key: questionForm
만약 HTML 파일(question/inputForm.html)에서 th:object="${form}" 또는 th:object="${questionForm}"과 같이 다른 이름을 찾고 있다면, Key 불일치로 오류(PropertyOrFieldInaccessibleException 등)가 발생합니다.
💡 해결 방법: HTML에서 호출하는 이름과 똑같이 맞추거나, @ModelAttribute("원하는이름")을 사용하여 이름을 명시해 주어야 합니다.
② 원인 2: DTO/Form 클래스에 Setter 또는 생성자가 없는 경우 스프링이 파라미터로 QuestionDto를 만들어 모델에 넣을 때, 기본 생성자와 Setter를 사용해 필드를 채웁니다. 만약 DTO에 Getter/Setter 설정이 빠져있거나 기본 생성자가 없으면, 데이터를 바인딩하는 과정에서 오류가 발생하거나 객체의 값이 null로 유지되어 뷰에 빈 값만 넘어가게 됩니다.
③ 원인 3: questionDto에 값을 새로 입력했지만 반영되지 않은 느낌을 받는 경우
questionDto.setSubject(question.getSubject()); questionDto.setContent(question.getContent()); 파라미터로 주입받은 questionDto에 DB에서 조회한 값을 직접 set 하셨습니다. 이처럼 기존 객체의 내부 필드값을 수정(setter)하는 것은 자동 생성된 Model에도 그대로 반영됩니다. 하지만 만약 메서드 내부에서 아래처럼 새로운 객체를 생성해서 할당해 버리면 문제가 생깁니다.
// ❌ 이렇게 하면 파라미터로 자동 등록된 Model 객체와 다른 객체가 되어 뷰에 전달되지 않음! questionDto = new QuestionDto(question.getSubject(), question.getContent());
이런 경우에는 반드시 model.addAttribute("questionDto", questionDto)를 써서 새로 만든 객체로 모델을 덮어씌워야 합니다.
3. 권장되는 작성 스타일 (Best Practice) 스프링의 자동 바인딩 원칙을 살려 코드를 가독성 있게 작성하는 표준 형태입니다. 방법 A. 스프링 기본 자동 등록 활용 (가장 깔끔함) Model 객체를 파라미터에서 제거하고 questionDto에 값만 세팅해 리턴합니다.
@GetMapping("/modify/{id}") public String modify(@PathVariable("id") Long id, QuestionDto questionDto, Principal principal) { Question question = questionService.getQuestion(id);
if (!question.getAuthor().getUsername().equals(principal.getName())) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정 권한이 없습니다."); } // 파라미터로 들어온 questionDto는 이미 Model에 'questionDto'라는 이름으로 들어가 있음 questionDto.setSubject(question.getSubject()); questionDto.setContent(question.getContent()); return "question/inputForm"; }
방법 B. 이름 명시하기 (@ModelAttribute 사용) HTML에서 어떤 이름으로 객체를 참조하는지 명확히 하고 싶다면 @ModelAttribute 어노테이션을 붙여주는 것이 원칙상 명확합니다.
@GetMapping("/modify/{id}") public String modify(@PathVariable("id") Long id, @ModelAttribute("questionDto") QuestionDto questionDto, Principal principal) { // ... 로직 동일 ... return "question/inputForm"; } |