스프링 쇼핑몰 만들기 #10. 상품 수정/삭제 기능 구현
이번에는 상품 수정/삭제 기능을 구현하겠습니다.
view.jsp의 폼(form)태그 내부에 코드를 추가합니다.
<input type="hidden" name="n" value="${goods.gdsNum}" />
보이지 않도록 숨겨둔(type="hidden") 인풋박스에 상품번호(gdsNum)을 저장해놨습니다.
실제로 보이진 않지만 값이 잘 저장되어있는걸 확인할 수 있습니다.
이제 view.jsp의 버튼부분을 수정합니다.
<div class="inputArea"> <button type="button" id="modify_Btn" class="btn btn-warning">수정</button> <button type="button" id="delete_Btn" class="btn btn-danger">삭제</button> <script> var formObj = $("form[role='form']"); $("#modify_Btn").click(function(){ formObj.attr("action", "/admin/goods/modify"); formObj.attr("method", "get") formObj.submit(); }); $("#delete_Btn").click(function(){ formObj.attr("action", "/admin/goods/delete"); formObj.submit(); }); </script> </div>
각 버튼에 맞는 컨트롤러의 메서드로 연결됩니다.
상품 등록과 상품 수정은 거의 같은 구조이기 때문에, register.jsp를 복사/붙여넣기 한 뒤, modify.jsp로 이름을 변경합니다.
또, 상품 등록 메서드를 복사하여 매핑된 주소와 메서드 이름을 수정합니다. jsp와 마찬가지로 컨트롤러 또한 거의 같은 구조입니다.
컨트롤러에서 부여한 model명을 이용하여, modify.jsp로 접속했을 때 데이터를 불러올 수 있도록 코드를 추가합니다.
<form role="form" method="post" autocomplete="off"> <input type="hidden" name="gdsNum" value="${goods.gdsNum}" /> <div class="inputArea"> <label>1차 분류</label> <select class="category1"> <option value="">전체</option> </select> <label>2차 분류</label> <select class="category2" name="cateCode"> <option value="">전체</option> </select> </div> <div class="inputArea"> <label for="gdsName">상품명</label> <input type="text" id="gdsName" name="gdsName" value="${goods.gdsName}"/> </div> <div class="inputArea"> <label for="gdsPrice">상품가격</label> <input type="text" id="gdsPrice" name="gdsPrice" value="${goods.gdsPrice}"/> </div> <div class="inputArea"> <label for="gdsStock">상품수량</label> <input type="text" id="gdsStock" name="gdsStock" value="${goods.gdsStock}"/> </div> <div class="inputArea"> <label for="gdsDes">상품소개</label> <textarea rows="5" cols="50" id="gdsDes" name="gdsDes">${goods.gdsDes}</textarea> </div> <div class="inputArea"> <button type="submit" id="update_Btn" class="btn btn-primary">완료</button> <button type="submit" id="back_Btn" class="btn btn-warning">취소</button> </div> </form>
인풋박스의 이름(name)속성과 값(value)속성을 추가했으며, 버튼 부분도 수정했습니다.
상품 조회 페이지(view.jsp)에서 수정 버튼을 눌러 상품 수정 페이지(modify.jsp)로 이동하면, 데이터가 잘 들어가 있습니다. 하지만 카테고리(분류) 부분은 아무런 변화가 없는데, 카테고리는 아래에 이어서 작업할 것입니다.
또한 수정과 취소 버튼에 대한 코드는 없어서 클릭하면 에러가 발생합니다.
먼저 취소 버튼의 기능을 추가합니다.
<button type="button" id="back_Btn" class="btn btn-warning">취소</button> <script> $("#back_Btn").click(function(){ //history.back(); location.href = "/admin/goods/view?n=" + ${goods.gdsNum}; }); </script>
가장 먼저 버튼의 타입을 submit에서 button으로 변경하고, 스크립트를 추가했습니다. history.back();
은 브라우저의 뒤로가기와 동일한 기능이며, location.href = "/admin/goods/view?n" + ${goods.gdsNum};
도 같은 기능입니다.
다음은 카테고리 부분입니다.
상품 테이블(tbl_goods)에는 상품코드(cateCode)만 저장되어있을 뿐이지 카테고리 이름(cateName)이나 카테고리 참조 코드(cateCodeRef)가 없기 때문에, 테이블 2개를 동시에 읽어오는 조인(join)을 이용해야합니다.
데이터 베이스에 2번 접속하여 상품 테이블과 카테고리 테이블을 따로따로 가져오는 방법도 있지만, 최대한 데이터 베이스에 접속하는 횟수를 줄이는게 무리를 주지 않기 때문에 조인할 수 있는 테이블은 조인해서 사용합니다.
아예 상품 테이블에 카테고리 이름과 카테고리 참조 코드를 모두 저장하는 방법도 있으나, 이러면 카테고리 테이블을 사용하는 의미가 없으며, 데이터 베이스에는 불필요한 중복 데이터를 저장하면, 말그대로 쓸모없는 데이터가 늘어나는것 뿐입니다.
조인해서 가져오는 값은 주로 사용될 예정이니 VO로 등록해둡니다. GoodsVO에서 카테고리 이름과 카테고리 참조 코드를 추가한 뒤, getter와 setter까지 추가하여 GoodsViewVO를 만들었습니다.
매퍼에서 기존에 사용하던 상품 조회(goodsView) 쿼리를 주석처리하고, 새로운 상품 조회 쿼리를 추가합니다.
<!-- 상품 조회 + 카테고리 조인--> <select id="goodsView" resultType="com.kubg.domain.GoodsViewVO"> select g.gdsNum, g.gdsName, g.cateCode, c.cateCodeRef, c.cateName, gdsPrice, gdsStock, gdsDes, gdsImg, gdsDate from tbl_goods g inner join goods_category c on g.cateCode = c.cateCode where g.gdsNum = #{gdsNum} </select>
from tbl_goods g inner join goods_category c
부분을 보면, tbl_goods 테이블은 g라는 이름으로 축약하여 사용하고, goods_catecory 테이블은 c라는 이름으로 축약하여 사용합니다.
즉, g.gdsNum 은 tbl_goods 테이블의 gdsNum 컬럼이라는 의미가 됩니다.
on g.cateCode = c.cateCode 는 조인의 조건인데, g.cateCode와 c.cateCode의 값이 일치하는것만 가져온다는 의미입니다.
기존에 사용하던 DAO와 Service의 결과값의 형태가 GoodsVO에서 GoodsViewVO로 변경되었으므로 이 부분을 수정합니다.
컨트롤러에서도 마찬가지로 GoodsVO에서 GoodsViewVO로 수정합니다.
이제 카테고리의 데이터를 가져올 수 있으므로, 스크립트에 코드를 추가합니다.
var select_cateCode = '${goods.cateCode}'; var select_cateCodeRef = '${goods.cateCodeRef}'; var select_cateName = '${goods.cateName}'; if(select_cateCodeRef != null && select_cateCodeRef != '') { $(".category1").val(select_cateCodeRef); $(".category2").val(select_cateCode); $(".category2").children().remove(); $(".category2").append("<option value='" + select_cateCode + "'>" + select_cateName + "</option>"); } else { $(".category1").val(select_cateCode); //$(".category2").val(select_cateCode); // select_cateCod가 부여되지 않는 현상이 있어서 아래 코드로 대체 $(".category2").append("<option value="' + select_cateCode + '" selected='selected'>전체</option>"); }
하위 카테고리가 없는 상위 카테고리만 설정된 상품 수정시, 하위 카테고리에 select_cateCode
가 제대로 부여되지 않으며, 이상태에서 수정을 하려고하면 에러가 발생됩니다. 제대로 확인하지 않고 작성하여서 이런 문제가 생겼습니다ㅠㅠ
$(".category2").val(select_cateCode);
를 주석 처리하고, 새로운 코드 $(".category2").append("<option value="' + select_cateCode + '" selected='selected'>전체</option>");
를 추가하면 정상적으로 실행됩니다.
이후에 새로운 프로젝트로 다시 작성하겠습니다.
이 코드는 카테고리 코드, 카테고리 참조 코드, 카테고리 이름을 가져와서 셀렉트 박스에 추가하는 역할입니다.
이제 수정 페이지에 접속해보면 카테고리까지 잘 표시되는걸 확인할 수 있습니다.
이제 수정 쿼리를 작성하고 테스트 해봅니다.
테이블의 값이 변경되었다면 꼭 commit;
을 실행합니다.
정상적으로 작동하는 쿼리를 매퍼에 추가합니다.
<!-- 상품 수정 --> <update id="goodsModify"> update tbl_goods set gdsName = #{gdsName}, cateCode = #{cateCode}, gdsPrice = #{gdsPrice}, gdsStock = #{gdsStock}, gdsDes = #{gdsDes} where gdsNum = ${gdsNum} </update>
상품 이미지(gdsImg)는 아직 없으므로 쿼리에서 제외했습니다.
DAO와 Service에 코드를 추가합니다.
다음은 컨트롤러에 코드를 추가합니다.
// 상품 수정 @RequestMapping(value = "/goods/modify", method = RequestMethod.POST) public String postGoodsModify(GoodsVO vo) throws Exception { logger.info("post goods modify"); adminService.goodsModify(vo); return "redirect:/admin/index"; }
상품 등록할 때와 크게 다르지 않습니다.
잘되는지 확인하기 위해 내용을 수정합니다.
잘되네요.
이번엔 삭제 쿼리를 작성하고 실행해봅니다.
정상적으로 작동하는 쿼리를 매퍼에 추가합니다.
<!-- 상품 삭제 --> <delete id="goodsDelete"> delete tbl_goods where gdsNum = #{gdsNum} </delete>
DAO와 Service에 코드를 추가합니다.
컨트롤러에도 코드를 추가합니다.
// 상품 삭제 @RequestMapping(value = "/goods/delete", method = RequestMethod.POST) public String postGoodsDelete(@RequestParam("n") int gdsNum) throws Exception { logger.info("post goods delete"); adminService.goodsDelete(gdsNum); return "redirect:/admin/index"; }
view.jsp에서, 삭제 버튼의 스크립트를 조금 수정합니다.
$("#delete_Btn").click(function(){ var con = confirm("정말로 삭제하시겠습니까?"); if(con) { formObj.attr("action", "/admin/goods/delete"); formObj.submit(); } });
confirm();
은 사용자에게 메시지를 보내서, 확인(true)과 취소(false)값을 받을 수 있는 다이얼로그입니다. 사용자가 선택한 결과로 조건문(if)을 이용하여 삭제 여부를 정할 수 있습니다.
다른 jsp로 이동하거나, 모달을 이용하는 방법도 있는데, 이번엔 이 방법을 사용해봤습니다.
삭제 버튼을 누르면 화면처럼 사용자에게 묻는 다이얼로그가 나타납니다. 브라우저마다 모양은 다르지만 결과는 같습니다.
취소를 선택하면 아무일도 발생하지 않지만, 확인을 선택하면 삭제 기능이 실행됩니다.
마지막 상품이 삭제되어서 아무것도 없게되었네요.
으.. 하다가 또 이틀째 멈췄습니다..ㅠㅠㅠ
답글삭제상품 수정을 하면 자연스럽게~~ index로 넘어갔지만.. 수정이 안되어있습니다.
깃허브 링크에서 modify.jsp를 보려했지만.. 파일이 없네요 ㅠㅠ
댓글에다가 변환해서 적으니 4096자 제한에 막혀서...또 다시 메일로 문의드립니다 ㅠㅠㅠ
gnum 값을 못받아오는 현상이었는데... 테이블에
답글삭제히든값을 넣어서 vo값인 goods에서 gnum을 불러와서 연결했습니다
modify.jsp에서 gnum값을 받아오는 부분을 제가 놓친건지요..ㅠㅠㅠ
아아....있었군요..다시 한번 역으로 올라가보니..히든값으로 있었군요 ㅠㅠ
답글삭제기존에 n을 히든으로 넣었던거랑 같은건줄알고 지나갔나봅니다 ㅠㅠ
해결했습니다;ㅅ;
메일 보고나서 잘못된게 있나 하고 게시물 보러왔는데 해결하셨네요:)
삭제헤헤...감사합니다^^
삭제히든 타입 input 태그의 name을 gdsNum으로 해줘야 넘어가는데 저도 n으로 놔둬서 같은 실수를 했네요
삭제아... 수정에서 카테고리가 왜 안불러와지짖.. 5번도 더보면서 확인했는뎅... 카테고리 호출까지는 잘가는데 스크립트가 실행안데는듯..
답글삭제var select_cateCode = '${goods.cateCode}';
답글삭제var select_cateCodeRef = '${goods.cateCodeRef}';
var select_cateName = '${goods.cateName}';
여기 스크립트 추가부분은 왜 해주시는거에요?
안하니까 수정이 잘되네여?
안녕하세요? goodsVO의 데이터를 사용하기 위해 해주는 부분입니다.
삭제본문 도중에 수정된 사항이 있는데, 그부분을 수정해주시면 정상적으로 작동할겁니다.
말씀하신 부분의 코드를 없앴는데 실행이 잘 된다면, 다른곳에 선언해서 에러가 발생한것 같습니다.
저도 에러나서 보니
삭제마지막 부분에
'" + select_cateCode + "' 부분에 따옴표 순서 변경하면 실행 잘 됩니다 :)
안녕하세요 . 잘보고 공부하고있습니다.
답글삭제다름이 아니라 수정부분 스크립트에서 오류가 나서 질문있는데 메일로 보냈어요 .
시간좀 내주시면 정말 감사하겠습니다 ㅠㅠ
답글삭제delete
tbl_goods
where gdsNum = #{gdsNum}
이부분에 from이 빠져있네요
안녕하세요? 방문해주셔서 감사합니다.
삭제다른 게시물쪽도 그렇고 지적 감사합니다. 새로 작성될 게시물에서는 빠지지않도록 꼼꼼하게 보겠습니다.
수정화면에 들어가 데이터값을 변경하고 완료를 누르면 index페이지로는 가는데 데이터가 수정은 되지않네요. 혹시 깃허브링크가 있을까요?? 저 위에 링크에는 소스가없네요 ㅠㅠ
답글삭제안녕하세요? 방문해주셔서 감사합니다.
삭제먼저 에러가 없는데 수정이 되지 않았다면, 쿼리의 조건문에 해당되는 데이터가 없기 때문입니다. 수정시 사용하는 데이터는 상품 번호(gdsNum)이므로, 프론트에서 컨트롤러로 넘어올 때 상품번호가 제대로 넘어왔는지 확인해보셔야할 것 같습니다.
확인하는 방법은 디버그모드나 로그, 출력등을 이용하시면 됩니다.
작성자가 댓글을 삭제했습니다.
삭제스크립트 추가 해줄때
답글삭제$(".category2").append ~~~~ << 마지막 이줄 때문에 스크립트 실행이 안되네요ㅠㅠ
안녕하세요? 방문해주셔서 감사합니다.
삭제음.. 여기까지 진행하셨다면 제이쿼리 임포트 문제는 아닐테고
append의 괄호에 들어가는 값들을 한줄에 입력해보셔도 에러가 발생한다면 코드를 올려주시면 확인하겠습니다.
안녕하세요. 상품조회 자바스크립트 부분을 하고 있는데요.
답글삭제modify.jsp로 넘어왔을 때, 등록되어 있는 옵션값대로 카테고리1[무기] 카테고리2[돌격소총] 이렇게 잘 보입니다.
하지만 바로 카테고리2의 옵션을 열면다른 옵션([기관단총].. 등 )이 보이지 않습니다.
즉, 등록되어있는 옵션 값 하나만 보입니다.
다른 카테고리1 옵션([전체],[탄] ...) 중 하나를 클릭했다가 다시 [무기]를 클릭하면 카테고리2의 옵션([전체],[돌격소총],[기관단총])이 제대로 나옵니다.
현재 이렇게 보이는 게 맞는 건지, 제가 잘못 입력해서 이렇게 나오는 건지 궁금해서 댓글 남깁니다.
그럼 오늘도 잘 보고 갑니다 :)
안녕하세요? 방문해주셔서 감사합니다.
삭제수정했을때에도 하위 카테고리가 모두 보여야 정상입니다..라고 댓글을 쓰려다가 뭔가 싸한 느낌이 들어 코드를 쭉보다보니 모두 삭제하고(remove()) 하나만 넣는 코드가 있네요;
기억력이 안좋아서 가물가물한데(...)
코드가 실행되는 순서가 이러한데
1. 상위/하위 카테고리는 기존 처럼 불러오고
2. js에서 상위 카테고리 selected 처리 (이때 하위 카테고리 추가됨)
3. js에서 하위 카테고리 selected 처리
이게 올바른 방법입니다.
해당 부분은... 나중에-_- 새로 쓰면서 손보겠..습니..다..ㅠㅠ
답글 감사합니다.
삭제remove()를 삭제하고, 다른 방법으로 자바스크립트를 제어해서 하려고 해봤는데 실패했어요 ��
언젠가 업로드 되면 참고하겠습니다. :)
아직도 이부분은 그대로네요 ㅠ
삭제저 부분이 select1에서 값이 바뀌어야 select2에 값이 들어가는 구조여서
삭제select1이 값이 바뀌지 않아도 select2에 값이 들어갈수 있도록 해주면 될거 같습니다.
저는 선택된 물건의 카테고리를 처음에 입력해 주는 과정에서 select1이 바뀔때 select2값이 들어가는 코드를 이용해서 넣었더니 값이 잘 들어가 있었습니다.
var select_catecode = '${goods.catecode}';
var select_catecoderef = '${goods.catecoderef}';
var select_catename = '${goods.catename}';
if (select_catecoderef != null && select_catecoderef != '') {
$(".category1").val(select_catecoderef);
//$(".category2").val(select_catecode);
$(".category2").children().remove();
$(".category2").append("<option value='" + select_catecoderef + "'>전체</option>");
//$(".category2").append("<option value='" + select_catecode + "' selected='selected'>" + select_catename + "</option>");
var cate2Arr = new Array();
var cate2Obj = new Object();
// 2차 분류 셀렉트 박스에 삽입할 데이터 준비
for (var i = 0; i < jsonData.length; i++) {
if (jsonData[i].level == "2") {
cate2Obj = new Object(); //초기화
cate2Obj.catecode = jsonData[i].catecode;
cate2Obj.catename = jsonData[i].catename;
cate2Obj.catecoderef = jsonData[i].catecoderef;
cate2Arr.push(cate2Obj);
}
}
var cate2Select = $("select.category2");
for (var i = 0; i < cate2Arr.length; i++) {
if (select_catecoderef == cate2Arr[i].catecoderef) {
if (select_catecode == cate2Arr[i].catecode) {
cate2Select.append("<option value='" + select_catecode + "' selected='selected'>" + select_catename + "</option>");
} else {
cate2Select.append("<option value='" + cate2Arr[i].catecode + "'>" + cate2Arr[i].catename + "</option>");
}
}
}
} else {
$(".category1").val(select_catecode);
//$(".category2").val(select_cateCode);
// select_cateCode가 부여되지 않는 현상이 있어서 아래 코드로 대체
$(".category2")
.append("<option value='" + select_catecode + "' selected='selected'>전체</option>");
}
감사합니다.!!
삭제이거네.. 초기화를 이용해서 값이 중복되지 않게 막아주신거 같은데 정말 감사합니다 해결했어요
삭제modify로 넘어왔을때 1차 분류에는 값이 있고 2차분류에 값이 없으면 어디가 문제인가요...?
답글삭제컨트롤러에서 확인했을때에는 값이 들어와 있습니다
삭제안녕하세요? 방문해주셔서 감사합니다.
삭제조회에서 수정(modify)으로 넘어왔는데, 컨트롤러에선 값이 있으니 뷰에서 값이 없다면..
아마 js쪽에 오타등으로 인한 에러가 발생한걸로 추측됩니다.
modify에 카테고리 값이 안들어오는데 1차분류엔 빈칸이고 2차분류에는 값이 들어오는데 어떤 부분을 봐야하나요?
답글삭제안녕하세요? 방문해주셔서 감사합니다.
삭제1차 분류에서 값이 안들어온다면.. script부분에서 category1쪽에 들어오는 데이터를 확인해야할 것 같습니다.
안녕하세요. 덕분에 열심히 공부하고 있습니다. modify 페이지가 스프링에서 실행했을 때는 안넘어가는데 chrome에서 실행했을 때는 잘 넘어갑니다. 어디서 확인해야 두 곳 다 잘 넘길 수 있게 끔 할 수 있나요? (삭제도 chrome에서만 동작하네요)
답글삭제안녕하세요? 방문해주셔서 감사합니다.
삭제스프링에서 실행했을때가.. 내장 브라우저를 사용한건가요?
일단 크롬에서 실행했을때의 로그를 보셔야할 것 같습니다
안녕하세요? 잘보고있습니다
답글삭제// 상품 수정
@RequestMapping(value = "/goods/modify", method = RequestMethod.POST)
public String postGoodsModify(GoodsVO vo) throws Exception {
logger.info("post goods modify");
adminService.goodsModify(vo);
return "redirect:/admin/index";
}
form 에서 넘어온값을 @RequestParam으로 받지않고 GoodsVO vo라고 하셨는데 어떻게 값을 받을수 있는건가요??
안녕하세요? 방문해주셔서 감사합니다.
삭제컨트롤러가 받는 변수의 형태가 GoodsVO와 이름(name)이 동일하게 되어있다면, 스프링이 자동으로 매칭시켜주기 때문에 가능합니다.
감사합니다 선생님 스프링 게시만 만들기에 다 나와있었네요.. ㅎㅎ
삭제또 하나 궁금한게 있는데 form 에서 action이 없는데 submit을 눌렀을때 어떻게 맞는 컨트롤러를 찾아가는지도 궁금합니다
답글삭제혼자공부하다보니 어려운게 많네요..
안녕하세요? 방문해주셔서 감사합니다.
삭제코드를 보시면.. form에는 action이 없으나
submit 버튼을 눌렀을때 action을 지정해주고 있습니다.
안녕하세요. 정말 많은 도움을 받고 있습니다.
답글삭제질문이 있어서 그러는데 혹시 죄송하지만
제이에스피 카테고리 부분이 이해가 되지 않네요 ㅠㅠ
select 1차분류 2차분류 부분에서 2차는 cateName으로 하는데
1차분류는 cateCode랑 cateCodeRef는 String으로 선언된 글자(100)인데 어떻게 무기라는 글자를 가져온거죠??
안녕하세요? 방문해주셔서 감사합니다.
삭제1차 분류와 2차 분류는 한개의 테이블을 이용합니다.
이때 테이블은 고유번호, 이름, 참조번호 3개의 컬럼으로 상/하위와 이름을 지정할 수 있습니다.
참조번호가 없다면, 즉 최상위 카테고리를 가져온다면 게시물의 본문처럼 가져올 수 있게되고
무기의 고유번호를 참조하는 다른 행을 찾게되면, 무기의 하위 카테고리를 불러올 수 있게 됩니다.
안녕하세요,
답글삭제delete가 되지 않아 댓글 남깁니다,, 정말 삭제하겠습니까? -> 확인을 누르면
콘솔창에 WARN : org.springframework.web.servlet.PageNotFound - No mapping for POST /admin/goods/delete
라 뜨고,
http://localhost/폴더명/폴더명/delete 404페이지로 이동됩니다
어느 부분을 봐야 좋을까요ㅠㅠ
안녕하세요? 방문해주셔서 감사합니다.
삭제컨트롤러로 넘기는 URL이 /admin/goods/delete1이라면(앞에 / 있어야합니다) 컨트롤러에서도 동일한 URL로 매핑이 되어 있어야 합니다.