[ ApiUtil ]
package com.example.store._core.utils; import lombok.Data; @Data public class ApiUtil<T> { private Integer status; private String msg; private T body; public ApiUtil(T body) { this.status = 200; this.msg = "성공"; this.body = body; } public ApiUtil(Integer status, String msg) { this.status = status; this.msg = msg; this.body = null; } }
json으로 받을 것이기 때문에 메세지 컨버터로 호출 → @Data 필요함!
[ ProductController ]
//상품명 실시간 중복체크 @GetMapping("/product/name-check") public @ResponseBody ResponseEntity<?> nameSameCheck(String name, HttpServletResponse response) { Product product = productService.findByName(name); if (product == null) { return ResponseEntity.ok(new ApiUtil<>(true)); //상품 등록 가능 } else { //response.setStatus(400); return ResponseEntity.ok(new ApiUtil<>(false)); //상품 등록 불가 } }
false 라고 통신 오류가 아님!
[ ProductService ]
//상품명 실시간 중복체크 public Product findByName(String name) { Product product = productRepo.findByName(name); return product; }
[ ProductRepository ]
//상품명 실시간 중복체크 //TODO: 레파지토리에서 try-catch를 안하고 싶은데... 어떻게 해야하지? public Product findByName(String name) { try { String q = """ select * from product_tb where name = ? """; Query query = em.createNativeQuery(q, Product.class); query.setParameter(1, name); Product product = (Product) query.getSingleResult(); return product; } catch (NoResultException e) { return null; } }
레파지토리에서 try-catch 잡아도 된다고 하심
[ save-form ]
{{> layout/header}} <div class="container" style="margin-top: 5%; margin-bottom: 5%"> <div class="row"> <!-- 이미지 업로드 섹션 --> <div class="col-lg-4 mb-4 mb-lg-0"> <form id="productForm" action="/upload" method="post" enctype="multipart/form-data"> <img src="https://m.lovelanc.com/web/product/big/Lbig230706-48ea_j27.jpg" width="300" height="300"> <div class="row"> <div class="form-group mt-3 col-10"> <input type="file" class="form-control" id="photo" name="imgFile" accept="image/*"> </div> <div class="form-group mt-3 d-flex justify-content-center"> <button type="submit" class="btn btn-success mt-2" style="color:#fff; margin-left: -20%">사진변경 </button> </div> </div> </form> </div> <!-- 상품 정보 입력 섹션 --> <div class="col-lg-6 px-xl-10"> <form action="/product/save" method="post"> <div class="mb-3 mt-3"> 상 품 명 : <input id="name" name="name" type="text" class="form-control" placeholder="상품명을 입력하세요"> <div class="alert alert-danger" id="nameCheck">상품명을 입력해주세요</div> </div> <div class="mb-3 mt-3"> 상품가격 : <input name="price" type="number" class="form-control" placeholder="상품가격을 입력하세요"> </div> <div class="mb-3 mt-3"> 상품수량 : <input name="qty" type="number" class="form-control" placeholder="상품수량을 입력하세요"> </div> <div class="d-flex justify-content-center"> <button type="submit" class="btn btn-primary mt-3">상품등록완료</button> </div> </form> </div> </div> </div> <script> // change (UX 가 좋지 않다) // keyup (너무 많은 이벤트를 호출한다) // 디바운스 (시간을 정해서 한번에 모았다가 호출한다) // 실시간 상품명 중복체크 (save용) $("#name").keyup(function (){ //this = 지금 현재 클릭한 것, val = 값 가져옴 var name = $(this).val(); // alert(name); //서버로 가서 id 중복체크 -> url과 입력 데이터는 바뀌면 안됨 -> Ajax //url -> /product/name-check //서버에서 전달되는 데이터를 result로 받음 -> 가져온 데이터가 null이면 사용 가능, 있으면 중복 var encodedName = encodeURIComponent(name); //이게 없으면 띄어쓰기 인식 안됨 $.ajax({ method: "GET", url: "/product/name-check?name="+encodedName }).done((res)=>{ console.log(res); if (res.body === true) { $("#nameCheck").removeClass("alert-danger"); $("#nameCheck").addClass("alert-success"); $("#nameCheck").text("사용 가능한 상품명 입니다."); } else { $("#nameCheck").removeClass("alert-success"); $("#nameCheck").addClass("alert-danger"); $("#nameCheck").text("중복된 상품명 입니다."); } }).fail((res)=>{ alert("통신 오류"); //이게 진짜 통신 오류! }); }); //천단위 구분자 찍기 $(document).ready(function() { // 가격 입력 필드에 대한 이벤트 리스너 추가 $('#price').on('input', function() { // 입력된 값에서 숫자만 추출 var number = $(this).val().replace(/[^0-9]/g, ''); // 천 단위 구분자 적용 var changeNum = number.replace(/\B(?=(\d{3})+(?!\d))/g, ','); // 변환된 값으로 입력 필드 업데이트 $(this).val(changeNum); }); $('#qty').on('input', function() { // 입력된 값에서 숫자만 추출 var number = $(this).val().replace(/[^0-9]/g, ''); // 천 단위 구분자 적용 var changeNum = number.replace(/\B(?=(\d{3})+(?!\d))/g, ','); // 변환된 값으로 입력 필드 업데이트 $(this).val(changeNum); }); // 폼 제출 전 실행될 이벤트 $('form').on('submit', function() { // 가격과 수량 입력 필드에서 천 단위 구분자 제거 var price = $('#price').val().replace(/,/g, ''); $('#price').val(price); // 수량 필드에 대해서도 같은 처리를 수행 var qty = $('input[name="qty"]').val().replace(/,/g, ''); $('input[name="qty"]').val(qty); }); }); </script> {{> layout/footer}}
나는 지금 keyup 이벤트를 선택했다 → ux는 좋지만 글자를 하나 칠 때마다 쿼리문이 계속 날아가서 퍼포먼스가 좋지 않을 듯 함
대안 1. change 이벤트 → tab 키를 눌렀을 때 비교가 되지만 UX가 좋지 않다
대안 2. 디바운스 → 시간을 정해서 한 번에 모았다가 호출한다.
즉, 입력하다가 키보드를 잠깐 쉴 때 이벤트가 발생하는 것
그러나… 아직은 할 필요가 없고, Debounce가 맞는지 확인이 필요함!
클라이언트 사이드 (웹 페이지): 사용자가 입력한 이름을 $("#nameCheck").load("/product/name-check?name=" + name, ... 코드를 통해 서버로 보낸다. 이 코드는 jQuery를 사용하여 /product/name-check라는 주소로 name이라는 정보를 쿼리 스트링(?name=사용자이름)으로 붙여서 요청한다. 서버 사이드 (Spring MVC 컨트롤러): @GetMapping("/product/name-check")으로 매핑된 nameSameCheck 메소드가 이 요청을 받는다. 요청에서 name 쿼리 스트링의 값을 자동으로 name 파라미터에 넣어준다. 그리고 이 이름으로 상품을 검색해서 이미 존재하면 "false"를, 존재하지 않으면 "true"를 반환. 즉, 웹 페이지에서 서버로 이름을 보내 확인하는 과정을 쿼리 스트링을 사용해 수행한다. 서버는 이 정보를 받아서 처리 후 결과를 다시 웹 페이지로 반환한다.
중복된 상품명이 없을 땐 console.log에 true
중복된 상품명이 있으니 false로 찍힘. AJAX 성공!
fail은 400이나 500이 뜰 때 alert 창이 뜨는 것!
[ @ResponseBody를 사용한 이유 ]
서버에서 클라이언트로 직접 문자열이나 데이터를 보내주기 위해서. 예를 들어, 여러분이 쇼핑몰에서 상품 이름이 이미 사용되고 있는지 확인하고 싶을 때, 서버는 그 상품 이름이 사용 가능한지 ("true") 또는 사용 불가능한지 ("false")를 알려줘야 함. 이때, @ResponseBody가 없으면, 스프링은 "true"나 "false"를 화면 이름으로 이해한다. 하지만 우리의 목적은 화면을 찾는 것이 아니라, 이 문자열 자체를 웹 페이지로 직접 보내주는 것! 그래서 @ResponseBody를 사용하여 메소드에서 반환된 "true"나 "false" 문자열을 바로 웹 페이지로 보내줄 수 있게 했다.
Share article