스프링 강의를 듣던 도중 thymeleaf select - option 선택 값을 controlle로 넘기고 그 값들로 하나의 주문만을 완료시키는 로직을 만들고 실습했다.
내가 이 글에 적는 문제는 한가지 만의 주문이 아닌 get요청을 받은 페이지에서 여러 개의 주문을 한번에 post 하려는 문제이다.
해당 컨트롤러 코드
@GetMapping(value = "/order")
public String createForm(Model model) {
List<Member> members = memberService.findMembers();
List<Item> items = itemService.findItems();
model.addAttribute("members", members);
model.addAttribute("items", items);
return "order/orderForm";
}
@PostMapping(value = "/order")
public String order(@RequestParam("memberId") Long memberId,
@RequestParam("itemId") Long itemId,
@RequestParam("count") int count) {
orderService.order(memberId, itemId, count);
return "redirect:/orders";
}
}
getMapping에서 addAttribute로 db에 저장된 멤버 리스트와 상품 리스트인 members, items를 thymeleaf로 전달한다.
thymeleaf에서 해당 값들을 option - select의 선택값으로 노출 시킬 수 있게 만든다.
get요청의 thymeleaf 코드를 보면
<form role="form" action="/order" method="post">
<div class="form-group">
<label for="member">주문회원</label>
<select name="memberId" id="member" class="form-control" onchange="able(this)">
<option value="">회원선택</option>
<option
th:each="member : ${members}"
th:value="${member.id}"
th:text="${member.name}" />
</select>
</div>
<div class="form-group">
<label for="item">상품명</label>
<select name="itemId" id="item" class="form-control" onchange="able(this)">
<option value="">상품선택</option>
<option th:each="item : ${items}"
th:value="${item.id}"
th:text="${item.name}" />
</select>
</div>
<div class="form-group">
<label for="count">주문수량</label>
<input type="number" name="count" class="form-control" id="count" placeholder="주문 수량을 입력하세요" onchange="sendJson(this)" onclick="" disabled>
</div>
<div id='result'></div>
<button onclick="orderArray()" id="btn" type="button" class="btn btn-primary" style="margin-top: 20px;">Submit</button>
</form>
각각
th:each="item : ${items}"
th:value="${item.id}"
th:text="${item.name}"
등의 형식으로 getMapping에서 넘긴 items 리스트에 저장된 값을 select - option 형식으로 노출시키고 선택할 수 있게 만들었다. (postMapping에서 name이 count인 input의 값도 전달받음)
이후 선택 값을 post 할 경우
postMapping에서 각각 필요한 값들의 select에서 @RequestParam으로 name이 같으면 해당 name의 value를 얻어 주문을 넣는 방식의 코드를 만들었다.
정리하자면 강의는 위 사진의 상황에서 post요청을 보내면 thymeleaf의 select 과 input 값을 controller의 postMapping으로 각각 단 한가지만의 데이터를 이용해 하나의 주문만을 완료 시킬 수 있다는 문제가 있었다.
주문
결과
한번에 2번 이상의 주문이 불가능하다.
코드를 따라치던 때 강사님이 postmapping 부분을 바꾸면 여러 개의 주문도 한번에 가능하다고 하셨다.
그래서 한번 해보기로 했다.
우선 어떻게 해야할지 감이 잡히지 않았다. 그래서 강의 q&a에 여러가지 주문 하는 방법을 검색해보니
이렇게 답변을 주셨다.
일단 네이버처럼 프론트만 구현하기로 했고 데이터를 넘기는 방법은 차차 생각하기로 했다.
이렇게 option선택 후 주문수량 까지 입력하면 버튼 위에 유저명, 상품명, 수량이 함께 만들어지게 구현하였다.
그렇다면 이제는 정말로 데이터를 보낼 차례였다. 데이터를 보내려면 js배열을 보내면 될 것 같았다.
우선 선택된 값을 토대로 배열을 만들어 주었다. 내가 만든 배열의 형태는 이러했다
이때부터 고통이 시작되었다. 어떻게 spring으로 보내지? 사실 답은 쉽게 찾을 수 있었지만 내가 많이 돌아갔다. 구글링 해보니 jqeury로 ajax를 사용하여 보내는 글들을 많이 봤는데 난 jquery를 딱히 배우지 않았기 때문에 자연스레 ajax를 사용하지 않고 controller로 보내는 방법을 찾았다.
2틀간 머리를 부여잡고 방법을 찾아봤는데 끝내 ajax를 사용할 수 있응 방법은 있긴한 것 같은데 자세한 코드나 설명이 없어서 해내지 못했다.
배열을 히든 input에 넣고 post로 보낼 때 한번에 보내라? 라고 하시는 분도 있었는데 이 방법은 잘 모르겠더라. 그래서 어쩔 수 없이 jqeury를 이용하여 ajax로 post 요청을 보내는 방법으로 데이터를 보내기로 하였다.
thymeleaf
let arr [];
let orderData = {
member: memberText,
memberId: memberValue,
item: itemText,
itemId: itemValue,
count: count.value
};
arr.push(orderData);
function orderArray() {
$.ajax({
data: JSON.stringify(arr),
url: "/order",
type: "POST",
contentType: "application/json; charset=UTF-8",
success: function (data) {
location.href = "/orders"
},
error: function() {
alert("error… ");
}
});
}
Controller
@ResponseBody
@RequestMapping (value = "/order", method = RequestMethod.POST)
public String order(@RequestBody String json) { //String json
try {
System.out.println(json);
} catch (Exception e) {
e.printStackTrace();
return "FAIL";
}
return "redirect:/orders";
}
jqeury를 처음 봤기 때문에 jqeury를 사용하여 ajax로 JSON을 보내는 방법을 알아내고 보내봤는데 처음에는 감도 안잡히고 계속 fail만 떴다.
우선 배열을 JSON.stringify로 문자열로 만들어 /order에 post로 보내면 된다. 그런데 contorller에서 @RequestBody의 type을 맞추기가 어려웠고 사실은 지금도 확실히 이해가 되지는 않는다. 일단은 ajax로 보낸 배열(JSON.sringify 해주었기 때문에 문자열인 상태) 을 바로 String으로 받았다.
@RequestBody String json
contorller에서 받아온 배열 출력값
여러가지 주문 json이 넘어온 것을 볼 수 있다
( json in java gradle에 추가 [ 설치 해줘야만 사용할 수 있음 ] 다운로드 수 가장 많은 버전으로 ) https://mvnrepository.com/artifact/org.json/json)
"[JSON in Java]"의 JSONarray, JSONobject를 사용해서 java에서 받아온 배열화 시켜주고 contorller에서 쓸 수 있다.
@ResponseBody
@RequestMapping (value = "/order", method = RequestMethod.POST)
public String order(@RequestBody String json) { //String json
System.out.println(json);
try {
JSONArray array = new JSONArray(json);
System.out.println(array);
}
} catch (Exception e) {
e.printStackTrace();
return "FAIL";
}
return "redirect:/orders";
}
받아온 string json을 JSONarray에 넣고 출력해보니 배열이 출력되었는데 에러와 함께 값이 출력됐다.
해당 에러는
받아온 String json에서 JSONarray로 JSON 배열화 시킬때 발생한 에러이고 JSONarray에 배열로 넣으려면 '[' 로 시작해야한다는 말이었다
근데 또 값은 제대로 들어오고 활용할 수 있었다.
이해가 되지 않았다.
에러랑 값이 같이 들어오는게 가능한가? 많은 생각이 들었다.
값을 찍어보면서 확인해보니 String json에 이런 값이 출력되었다.
이게 뭐지? 하고 보니 HTML select-option 값이 ajax에서 보낸 json뿐 아니라 두 개의 값이 함께 넘어왔다. 즉, HTML form 값이 함께 넘어온 것 이다.
? 그래서 왜 값이 두개가 넘어왔지 여기서 멘붕이 왔다. 왜지.
그러다가 어떤 분께 여쭤보니 값이 두개가 넘어오는게 아니라 두번 출력된다고 하셨다.? 이건 또 뭔 소리람? html 버튼 형식을 submit -> button 형식으로 바꾸라고 하셨다. 그랬더니 에러가 없어지고 깔끔하게 json 값만 출력되었다.
알고보니 ajax를 설정해주면 알아서 submit해주고 POST 요청을 보내는 것이었다. ajax에서 post하고 html에서 또 post해서 form 두번 전송된 것이 었다.
jquery나 ajax를 처음 접해본 나로썬 알길이 없었다. 좋은 정보와 함께 에러를 해결하니 3일간 고통받았는데 너무나 행복했다.
그럼 이제 JSON을 사용해 주문 넣을 로직을 짤 수 있게 되었다.
@ResponseBody
@RequestMapping (value = "/order", method = RequestMethod.POST)
public String order(@RequestBody String json) { //String json
System.out.println(json);
try {
JSONArray array = new JSONArray(json);
System.out.println(array);
for (int i = 0; i < array.length(); i++) {
JSONObject jsonObject = (JSONObject)array.get(i);
//orderService에 넘길 멤버 Id
String MemberId = (String) jsonObject.get("memberId");
Long MemberIdToInt = Long.parseLong(MemberId);
//orderService에 넘길 아이템 Id
String itemId = (String) jsonObject.get("itemId");
Long itemIdToInt = Long.parseLong(itemId);
//orderService에 넘길 주문 수량
String count = (String) jsonObject.get("count");
int OrderCount = Integer.parseInt(count);
orderService.order(MemberIdToInt, itemIdToInt, OrderCount);
}
return "redirect:/orders";
} catch (Exception e) {
e.printStackTrace();
return "FAIL";
}
}
받은 JSON string을 각각 JSONarray로 배열화 시켜주고 for문으로 하나씩 받아 JSON 객체화 해주어 value값을 사용할 수 있다.
해당 value들을 service 형식에 맞게 보내면 주문완료 시킬 수 있다.
이렇게 한번에 2개 이상을 주문 할 수 있게 되었다.