만약
<form/>
에서 submit 할 요소들이 바인딩 될 객체 VO 안에 VO 가 있는 아래와 같다면 어떻게 보내야 할까.
각 VO 클래스에는 아래 lombok 어노테이션이 선언된 상태이다.
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
그리고 MyVO1 객체를 받는 컨트롤러 메서드가 있다.
@RequestMapping("api/post/MyVO1")
public String post(@ModelAttribute MyVO1 myVO1) {
//...
return "viewName";
}
이와 같이 VO1 과 VO2 가 1:n 형태를 갖을 때 컨트롤러에서 VO 에 바인딩하려면 <form/>
에서 제출할 요소의 name 속성값을 아래 처럼 설정해야 한다.
(편의상 List 길이는 2개 로 하였다.)
<form action="/api/post/MyVO1" method="post">
<button type="submit">제출</button>
<input type="text" name="value1" value="VALUE1">
<input type="text" name="value2[0]" value="VALUE2_0">
<input type="text" name="value2[1]" value="VALUE2_1">
<input type="text" name="myVO2[0].name1" value="NAME1_0">
<input type="text" name="myVO2[1].name1" value="NAME1_1">
<input type="text" name="myVO2[0].name2[0]" value="NAME2_00">
<input type="text" name="myVO2[0].name2[1]" value="NAME2_01">
<input type="text" name="myVO2[1].name2[0]" value="NAME2_10">
<input type="text" name="myVO2[1].name2[1]" value="NAME2_11">
</form>
로그를 찍어보니 정상적으로 값이 들어오는것을 확인 하였다.
컨트롤러 메서드를 다음과 같이 변경해보았다.
@ModelAttribute
는 위에서는 VO 이름 그대로 사용하는경우 기본값이 적용되어 생략 가능하지만
바인딩할 인스턴스명을 myVO1s 로 변경하기위해 추가 선언했다.
@RequestMapping("api/post/MyVO1s")
public String post(@ModelAttribute("myVO1s") List<MyVO1> myVO1s) {
//...
return "viewName";
}
그리고 MyVO1 자체를 리스트로 받기위해 폼에서 name 속성값도 인덱스를 적용했다.
<form action="/api/post/MyVO1s" method="post">
<button type="submit">제출</button>
<input type="text" name="myVO1s[0].value1" value="VALUE1">
<input type="text" name="myVO1s[0].value2[0]" value="VALUE2_0">
<input type="text" name="myVO1s[0].value2[1]" value="VALUE2_1">
<input type="text" name="myVO1s[0].myVO2[0].name1" value="NAME1_0">
<input type="text" name="myVO1s[0].myVO2[1].name1" value="NAME1_1">
<input type="text" name="myVO1s[0].myVO2[0].name2[0]" value="NAME2_00">
<input type="text" name="myVO1s[0].myVO2[0].name2[1]" value="NAME2_01">
<input type="text" name="myVO1s[0].myVO2[1].name2[0]" value="NAME2_10">
<input type="text" name="myVO1s[0].myVO2[1].name2[1]" value="NAME2_11">
<input type="text" name="myVO1s[1].value1" value="VALUE1">
<input type="text" name="myVO1s[1].value2[0]" value="VALUE2_0">
<input type="text" name="myVO1s[1].value2[1]" value="VALUE2_1">
<input type="text" name="myVO1s[1].myVO2[0].name1" value="NAME1_0">
<input type="text" name="myVO1s[1].myVO2[1].name1" value="NAME1_1">
<input type="text" name="myVO1s[1].myVO2[0].name2[0]" value="NAME2_00">
<input type="text" name="myVO1s[1].myVO2[0].name2[1]" value="NAME2_01">
<input type="text" name="myVO1s[1].myVO2[1].name2[0]" value="NAME2_10">
<input type="text" name="myVO1s[1].myVO2[1].name2[1]" value="NAME2_11">
</form>
하지만 다음과 같은 오류가 발생한다.
No primary or single unique constructor found for interface java.util.List
스프링은 요청 파라미터를 VO 객체를 컨트롤러에서 묵시적으로 객체를 생성 할 때,
java.util.List 같은 인터페이스나 추상클래스는 컬렉션이 어떤 구체적인 객체들을 포함해야 하는지를 알 수 없다. 따라서 스프링은 이를 인스턴스화할 수 있는 방법을 찾지 못하고 위와 같은 오류를 발생시킨다.
그래서 구체적인 구현 클래스 사용하는 것으로 변경하였다.
@RequestMapping("api/post/MyVO1s")
public String post(@ModelAttribute("myVO1s") ArrayList<MyVO1> myVO1s) {
//...
return "viewName";
}
이로써 생성자 오류 문제는 해결 되었다.
이제 MyVO1 의 Setter 함수를 통해 멤버변수에 자동으로 바인딩하려고 시도할 것이다. 그러나 MyVO1 안에는 List<MyVO1> 타입 변수가 없어 적절한 Setter 또한 찾지 못하게 된다.
결론 : <form> 에서 전송한 데이터의 Content-Type 은 application/x-www-form-urlencoded 이고 스프링 MVC 컨트롤러에서 @ModelAttribute 를 통해 파라미터 타입에 해당하는 객체를 생성한다.
파라미터 이름과 일치하는 객체의 필드에 값이 바인딩된다.
이때, 필드 이름과 파라미터 이름이 일치해야 하며, WebDataBinder가 이 과정을 관리합니다.
파라미터 이름과 선언한 객체의 인스턴스명이 같다면 @ModelAttribute 는 생략 가능하다.
바인딩은 생성된 인스턴스 의 Setter() 를 통해 바인딩 되므로 List<MyVO1> 타입의 멤버 변수가 필요하다.
List<MyVO1> 타입 멤버변수를 갖는 MyVO 를 사용하기로 했다.
MyVO 를 받는 컨트롤러로 변경하였다.
@RequestMapping("api/post/MyVO")
public String post(MyVO myVO) {
//...
return "viewName";
}
<form action="/api/post/MyVOs" method="post">
<button type="submit">제출</button>
<input type="text" name="myVO1s[0].value1" value="VALUE1">
<input type="text" name="myVO1s[0].value2[0]" value="VALUE2_0">
<!-- 이하 생략 -->
</form>
이로써 컨트롤러에 객체 바인딩이 정상적으로 됨을 확인하였다.
그냥 JSON 쓰자
끝.
수정이 필요한 내용이나 더 좋은 방법이 있으면 댓글로 남겨주세요. 감사합니다.