
[ 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