10/16/2018

스프링 쇼핑몰 만들기 #18. 상품 소감(댓글) 에이젝스(Ajax) 적용

스프링 쇼핑몰 만들기
- 깃허브 링크

쇼핑몰에서 상품 조회시, 이미지가 다수 첨부되어있기 때문에 데이터 소모가 큽니다. 그런데 상품 소감(댓글)을 작성 할 때마다 새로고침이 발생하게되면 불필요한 데이터 소모가 생깁니다.

새로고침이 발생하여 페이지를 새로 읽게되면 데이터 베이스에 접속해야하고, 동시에 첨부된 파일이 있는 저장소에도 접속해야합니다. 한두명이 한두번 한다면 문제가 되지 않겠지만 수백명 이상이 수백번 이상 한다면 문제가 발생할 수 있고, 문제가 발생하지 않더라도 이렇게 불필요한 데이터 소모가 일어나는걸 알면서도 방치할 수 없습니다.

그래서 상품 소감(댓글) 작성시 상품 소감 부분만 새로고침하게되면, 필요한 부분만 새로 읽어오는거니 불필요한 데이터 소모를 최소화 할 수 있습니다. 이런 이유로 비동기 전송방식인 '에이젝스(Ajax)'를 사용합니다.

에이젝스를 사용하려면, 가장 먼저 pom.xml에 라이브러리를 추가해야합니다.

pom.xml의 <dependencies> ~ </dependencies> 에 위의 코드를 추가합니다.

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.6</version>
</dependency>

<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-mapper-asl</artifactId>
    <version>1.9.13</version>
</dependency>

jackson-databindjackson-mapper-asl를 추가하면 됩니다.

jackson-databind의 경우 이 블로그의 게시물을 참고하여 진행했다면 이미 추가되었을 수 있습니다.

먼저, view.jsp에서 소감 목록을 주석처리합니다.

주석처리된 소감 목록의 </ol> 아래에 스크립트를 추가합니다.

<script> 
 var gdsNum = ${view.gdsNum};
 $.getJSON("/shop/view/replyList" + "?n=" + gdsNum, function(data){
  var str = "";
  
  $(data).each(function(){
   
   console.log(data);
   
   var repDate = new Date(this.repDate);
   repDate = repDate.toLocaleDateString("ko-US")
   
   str += "<li data-gdsNum='" + this.gdsNum + "'>"
     + "<div class='userInfo'>"
     + "<span class='userName'>" + this.userName + "</span>"
     + "<span class='date'>" + repDate + "</span>"
     + "</div>"
     + "<div class='replyContent'>" + this.repCon + "</div>"
     + "</li>";           
  });
  
  $("section.replyList ol").html(str);
 });
</script>

$.getJson() 은 비동기식으로 제이슨(Json) 데이터를 가져오는 메서드입니다.

http:///shop/view/replyList" + "?n=" + gdsNum 주소로 컨트롤러에 접속하여 데이터를 가져오고, 그 데이터를 이용해 HTML코드를 조립하여 <ol> 태그에 추가하는 방식입니다.

이때, 테이블에 저장된 날짜 데이터와 컨트롤러에서 뷰로 보낼때의 날짜 데이터 형식이 다르기 때문에, 컨트롤러에서 toLocaleDateString() 를 이용해 1차적으로 데이터를 가공했습니다.

주소에 맞는 메서드를 생성합니다. 메서드 내부 코드는 상품 조회용 메서드에서 사용한 코드를 그대로 사용할 수 있습니다.

// 상품 소감(댓글) 목록
@ResponseBody
@RequestMapping(value = "/view/replyList", method = RequestMethod.GET)
public List<ReplyVO> getReplyList(@RequestParam("n") int gdsNum) throws Exception {
 logger.info("get reply list");
   
 List<ReplyVO> reply = service.replyList(gdsNum);
 
 return reply;
} 

소감 목록을 읽어오는 메서드가 따로 생성되었으니, 상품 조회용 메서드에 있는 소감 목록 부분은 이제 필요가 없으니 주석처리합니다.

이제 프로젝트를 실행하고 상품 조회 페이지에 접속하면 예전처럼 상품 목록이 잘 출력되는걸 알 수 있습니다.

이 상품 소감 목록은 상품 조회 페이지와 동시에 출력되는게 아니라, 상품 조회 페이지 내부에서 상품 소감 목록이 출력되는 형태입니다.

이제 상품 소감 목록을 불러오는 스크립트를 하나의 함수로 묶습니다. 이렇게 함수화 하는 이유는 재사용하기 위해서입니다.

처음 상품 조회 페이지에 접속할 때 상품 소감 목록을 출력하고, 상품 소감을 새로 작성하거나, 수정하거나, 삭제할 경우에도 상품 소감 목록을 다시 출력해야하기 때문입니다.

이제 이 스크립트 자체를 헤드 <head> ~ </head> 내부로 옮깁니다. 이처럼 함수로 사용될 스크립트들은 특별한 이유가 있는게 아니라면 헤드 내부에 위치해둡니다.

원래 스크립트가 위치했던곳엔 함수를 호출하는 스크립트만 추가합니다.

<script>
 replyList();
</script>

다음은 소감 작성 부분입니다.

상품 번호가 저장된 인풋박스에 아이디를 설정하고, 소감 남기기 버튼의 타입을 submit 에서 button 으로 변경한 뒤 바로 아래에 스크립트를 추가합니다.

<script>
 $("#reply_btn").click(function(){
  
  var formObj = $(".replyForm form[role='form']");
  var gdsNum = $("#gdsNum").val();
  var repCon = $("#repCon").val()
  
  var data = {
    gdsNum : gdsNum,
    repCon : repCon
    };
  
  $.ajax({
   url : "/shop/view/registReply",
   type : "post",
   data : data,
   success : function(){
    replyList();
   }
  });
 });
</script>

gdsNum, repCon 변수를 선언하고, 인풋박스와 텍스트에어리어의 값을 저장합니다. 이 변수를 이용해 제이슨(Json)형 변수 data를 생성합니다.

변수 data가 좀 햇갈릴 수 있어서 잠깐 설명을 하자면, 제이슨 형태의 데이터는 { 키 : 값, 키 : 값, 키 : 값, 키 : 값 ... } 형태로 되어있습니다.

그러므로 { gdsNum : gdsNum, repCon : repCon } 는 글씨는 똑같지만 앞에있는것이 키, 뒤에있는것이 값이 됩니다.

이제 에이젝스를 이용하는데, 여기서 사용한 에이젝스는 url, type, data, success로 구성되어있습니다. url은 데이터가 전송될 주소, type는 타입(get, post), data는 전송될 데이터, success는 데이터 전송이 성공되었을 경우 실행할 함수부입니다.

이외에도 데이터 전송이 실패할 경우 실행되는 error, 성공 여부 상관없이 실행되는 complete등이 있습니다.

success에서는 위에서 작성한 replyList() 함수를 호출하여, 소감이 작성 완료되면 소감 목록을 다시 읽어서 출력합니다.

에이젝스가 전송될 주소에 맞게, 컨트롤러에 메서드를 추가합니다. 기존에 사용하던 상품 조회 POST 메서드는 이제 필요 없으니 주석처리합니다.

// 상품 소감(댓글) 작성
@ResponseBody
@RequestMapping(value = "/view/registReply", method = RequestMethod.POST)
public void registReply(ReplyVO reply,  HttpSession session) throws Exception {
 logger.info("regist reply");
 
 MemberVO member = (MemberVO)session.getAttribute("member");
 reply.setUserId(member.getUserId());
 
 service.registReply(reply);
} 

잘 작동되는지 확인하기 위해 소감을 남겨보겠습니다.

페이지가 새로고침되지 않았지만 새로운 소감이 추가된걸 확인할 수 있습니다.

소감이 등록되었으나, 텍스트에어리어에는 아직도 입력했던 내용이 남아있습니다.

에이젝스의 success 함수에 텍스트에어리어를 초기화하는 코드를 추가합니다.

$("#repCon").val("");

이제 상품 소감을 입력하면 소감 부분만 바뀌게되며, 동시에 텍스트에어리어도 초기화됩니다.

게시물 수정
  1. 작성자가 댓글을 삭제했습니다.

    답글삭제
  2. 혹시 댓글 작성시 content값이 null로 전달되어 쿼리 실행이 안되신다면 $("textarea#content").val(); 로 한번 스크립트 문을 바꿔보세요~~
    $(content).val()로 하는데 계속 에러나서~~
    $("텍스트어리어#텍스트어리어 id").val()로 했는데 잘되네요.
    잘보고 있습니다 ^0^

    답글삭제
  3. 감사합니다ㅠㅠㅠㅠ

    답글삭제
  4. 바로 등록이 안되고 새로고침을 해야 등록이 되는데 왜 그런걸 까요?

    답글삭제
  5. 익명9/04/2019

    안녕하세요? 좋은강좌 열심히 보고 있습니다. 감사드립니다.

    동적 HTML 처리시 $("section.replyList ol").html(str); 크롬에서는 정상적으로 처리되는데 IE11 에서는 처리가 되지 않습니다.
    페이지 새로고침을 해보면 정상적으로 처리되어 있습니다.
    원인이 무엇일까요?

    답글삭제
    답글
    1. 안녕하세요? 이 프로젝트는 크롬, 파이어폭스 기준으로 작성된거라 ie에서는 확인을 해봐야할것 같습니다. 죄송합니다ㅜㅜ

      삭제
  6. 새로고침 문제에 관련해서 저는
    https://epthffh.tistory.com/entry/ajax-%EB%A1%9C%EB%94%A9%EC%8B%9C-%EC%83%88%EB%A1%9C%EA%B3%A0%EC%B9%A8-%EB%AC%B8%EC%A0%9C
    참고했습니다..

    답글삭제
  7. 익명5/27/2020

    잘 보고있습니다.
    댓글 목록 출력 함수 부분에서 콘솔창을 열어 확인해보면 사용자 아이디, 날짜 데이터, 댓글 내용등은 잘 받아와 지는데
    정작 출력에서 댓글 내용만 undefined로 표시가 되어서 나오는데 파싱은 문제없이 된거같은데 뿌려주는 부분에서 뭔가 문제가 있는거 같은데..
    디비컬럼 명이랑 vo객체명 다 통일 시켜서 이름을 잘못써서 그런거 같지는 않고,, 혹시 이런 문제 겪어보신 분 있으신가요??

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      콘솔창으로 출력했을 때 나오는데 출력할때 문제가 생긴다면...
      아무래도 replyList에서 처리하는 과정에 문제가 있는것 같습니다.

      또는 모든 이름이 같으나 배열형식으로 받아와서(이부분은 컨트롤러~dao쪽 코딩문제이지만 에러는 아닙니다) data가 배열로 이루어져있을 가능성도 있습니다.

      삭제
  8. 익명6/26/2020

    항상 감사합니다.
    이 때 까지 잘 따라왔으나 여기에서 오류가 발생하여 문의드립니다.
    "ORA-02291: 무결성 제약조건(SCOTT.TSHOP_REPLY_GDSNUM)이 위배되었습니다- 부모 키가 없습니다"
    이런식으로 에러가 발생하는데 소감을 쓰고 소감남기기 를 클릭하면 gdsNum이 0의 값을 넘기는데 이 문제에 대해서 염치 불구하고 해결방법좀 부탁드리겠습니다.

    답글삭제
    답글
    1. 익명6/26/2020

      아 해결했어요! input태그에 gdsNum ID값을 안넣어 줬었네요! 감사합니다.

      삭제
  9. 앗 저도 따라하다가 댓글이 모두 undefinedInvalid Date
    undefined 이런식으로 출력이되네요 ㅠㅠ 이런경우에는 어떻게 해야하는건지....

    답글삭제
    답글
    1. db에서는 모두 문제없이 들어가져있는데 저도 뿌려주는부분에서 그런건지 모르겠네요 ㅠㅠ

      삭제
    2. 안녕하세요? 방문해주셔서 감사합니다.

      프론트, 그러니까 에이젝스로 받은 결과를 화면에 출력할때 undefined, Invalid Date가 나온다면..

      1. 출력할때 사용한 코드의 대소문자를 구분
      2. 컨트롤러에서 정상적인 데이터를 받았는지의 여부

      를 확인해보셔야할 것 같습니다.


      1번의 경우, 매퍼에서 받은 데이터가 대소문자를 구분하기 때문이고
      2번의 경우, 컨트롤러에 필요한 데이터가 전송되지 않았을 수 있기 때문입니다.

      로그 또는 출력 코드를 이용하여 확인할 수 있습니다.

      삭제