submit 을 통해 spring MVC 컨트롤러에서 다중 VO에 바인딩 하기

유재영·2024년 4월 14일
0

spring

목록 보기
3/3
post-custom-banner

만약 <form/> 에서 submit 할 요소들이 바인딩 될 객체 VO 안에 VO 가 있는 아래와 같다면 어떻게 보내야 할까.

  • MyVO1
    └─ String value1;
    └─ List<String> value2;
    └─ List<MyVO2> myVO2;
  • MyVO2
    └─ String name1;
    └─ List<String> name2;

각 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>

로그를 찍어보니 정상적으로 값이 들어오는것을 확인 하였다.


그럼 submit 으로 컨트롤러에서 List<MyVO1> 형태로 받는 것도 가능할까??

컨트롤러 메서드를 다음과 같이 변경해보았다.
@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
    └─ List<MyVO1> myVO1s;

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 쓰자
끝.


수정이 필요한 내용이나 더 좋은 방법이 있으면 댓글로 남겨주세요. 감사합니다.

profile
#java #javascript #spring #jquery #mariadb #vue.js
post-custom-banner

0개의 댓글