Spring Controller로 JSON 넘기기

0
post-thumbnail

스프링 강의를 듣던 도중 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 요청을 보내는 방법으로 데이터를 보내기로 하였다.

ajax로 배열 넘기기

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

JSON 객체화 하기


contorller에서 받아온 배열 출력값
여러가지 주문 json이 넘어온 것을 볼 수 있다


그러나 위 json은 아직 문자열일 뿐. 다음으로 각각의 value들을 사용할 수 있도록 값을 사용할 수 있게 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개 이상을 주문 할 수 있게 되었다.

profile
I want to be a Backend Developer (run start-up)

0개의 댓글