ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [4-ch17 댓글 처리 ④] 댓글의 페이징 처리 (인덱스 생성, 화면 처리)
    Back-End/Spring Legacy 2022. 9. 6. 14:01

     

    앞의 포스팅에서는 해당 게시물의 전체 댓글을 가져와서 화면에 출력한다. 문제는 댓글의 수가 엄청 많을 경우이다. 댓글 숫자가 많다면 데이터베이스에서 많은 양의 데이터를 가져와야 되고, 이는 성능상의 문제를 가져올 수 있다. 

     

     

    데이터베이스의 인덱스 설계

     

    댓글에 대해 우선적으로 고려해야 하는 일은 tbl_reply 테이블을 접근할 때 댓글의 번호(rno) 중심이 아니라, 게시물 번호(bno)가 중심이 된다는 점이다. 댓글을 조회할 때에는 해당 게시물의 댓글을 가져오기 때문에 'tbl_reply where bno = 200 order by rno asc'와 같은 방식으로 접근하게 된다. 

     

    효율을 높이고 싶다면 게시물의 번호에 맞게 댓글을 모아서 빠르게 찾을 수 있는 구조로 만드는 것이 좋다. 

     

     

    위와 같은 구조를 이용하게 되면 'bno = 200'와 같은 쿼리를 실행할 때 왼쪽 구조에서 bno가 200에 해당하는 범위만 찾아서 사용하면 된다. 이러한 구조를 생성하는 것을 '인덱스를 생성한다'고 표현한다. 

     

    앞에서 게시판 게시글의 페이징 처리할 때 인덱스에 대해 알아보았다. order by 보다는 인덱스를 이용해 정렬을 생략하는 방법인데, 게시글 페이징 처리에서는 테이블을 생성할 때 제약조건 pk를 지정했다. 그 pk로 지정한 컬럼 값이 bno 게시글 번호였는데, pk로 지정함으로써 자동으로 bno라는 컬럼을 기준으로 인덱스를 생성했다. 

     

    tbl_reply 역시 pk로 rno를 지정함으로써 자동으로 rno를 기준으로 인덱스가 생성되었다. 하지만! 위에서 말했듯이 게시글의 댓글에 대한 페이징 처리는 게시글 번호(bno)가 중심이 된다. 따라서 인덱스를 하나 생성해줘야 한다. 

     

    create index idx_reply on tbl_reply(bno desc, rno asc); 

     

     

     

    인덱스를 이용한 페이징 쿼리

     

    인덱를 이용하는 이유 중 하나는 정렬을 피할 수 있기 때문이다. 특정한 게시물의 rno의 순번대로 데이터를 조회하고 싶다면 다음과 같은 쿼리를 작성하게 된다. 

     

    select /*+ INDEX(tbl_reply idx_reply) */
    rownum rn, bno, rno, reply, replyer, replyDate, updatedate
    from tbl_reply where bno = 148 --게시물 번호
    and rno >0;

     

     

     

    실행된 결과를 보면 'IDX_REPLY'를 이용해 테이블에 접근하는 것을 볼 수 있다. 테이블에 접근해서 결과를 만들 때 생성되는 ROWNUM은 가장 낮은 rno 값을 가지는 데이터가 1번이 된다.

     

     

     

     

    ROWNUM이 원하는 순서대로 나오기 때문에 페이징 처리는 이전에 게시물 페이징과 동일한 형태로 작성할 수 있다. 예를들어 10개씩 2페이지를 가져온다면 아래와 같은 쿼리문을 작성할 수 있다.

     

    select rno, bno, reply, replyer, replydate, updatedate
    from(
    select /*+ INDEX(tbl_reply idx_reply) */
    rownum rn, bno, rno, reply, replyer, replyDate, updatedate
    from tbl_reply
    where bno = 148
    and rno >0
    and rownum<=20
    )
    where rn > 10;

     

    먼저 쿼리문을 작성해본 후 원하는 결과값이 나왔다면 이를 MyBatis에 적용하자! 만약) sql에서 데이터를 삽입하거나 삭제, 수정 등 데이터 변경이 일어났다면 꼭 COMMIT;

     

     


     

    <ReplyMapper.java 인터페이스 추가>

    public List<ReplyVO> getListWithPaging(
            @Param("cri") Criteria cri, @Param("bno") Long bno);

     

     

    <ReplyMapper.xml 수정>

     

    <!-- 게시글 번호에 해당하는 댓글 조회 및 댓글 페이징 -->
    <select id="getListWithPaging" resultType="org.zerock.domain.ReplyVO">
    
      <![CDATA[
     select  rno, bno, reply, replyer, replydate, updatedate
     from 
       (
        select /*+INDEX(tbl_reply idx_reply) */ 
          rownum rn,  rno, bno, reply, replyer, replyDate, updatedate
        from tbl_reply
        where bno =  #{bno}
        and rno > 0
        and rownum <= #{cri.pageNum} * #{cri.amount}
       ) where rn > (#{cri.pageNum} -1) * #{cri.amount}
    ]]>
    
    </select>

     

     

    <ReplyMapperTests.java>

     

    @Test
    public void testList2() {
        Criteria criteria = new Criteria(1,10);
        List<ReplyVO> replies = mapper.getListWithPaging(criteria, 148L);
        replies.forEach(reply -> log.info(reply));
    }

     

     

    테스트 진행했을 때 에러사항!

    java.sql.SQLSyntaxErrorException: ORA-00907: missing right parenthesis

     

     

    우괄호가 없다는 에러메시지가 출력됐다. xml을 살펴보니 마지막 where 구문에 우괄호가 없는 걸 확인 후 수정. 게시글 번호 148번에 해당하는 댓글 중 2페이지에 해당하는 댓글 출력 확인.

     

     

     

     

    댓글의 숫자 파악 - 영속계층

     

    댓글을 페이징 처리하기 위해 해당 게시물의 전체 댓글 숫자를 파악해서 화면에 보여줄 필요가 있다. 그래야 댓글 만큼의 페이징 처리를 할 수 있기 때문에. 

     

     

    < ReplyMapper.java 인터페이스 추가 >

    public int getCountByBno(Long bno);

     

    <ReplyMapper.xml 추가>

    <!-- 댓글의 숫자 파악 -->
    <select id="getCountByBno" resultType="int">
    <![CDATA[
        select count(rno) from tbl_reply where bno=#{bno}
    ]]>
    </select>

     

     

     

    댓글 숫자 파악 - Service 

     

     

    단순히 댓글 전체를 보여주는 방식과 달리 댓글 페이징 처리가 필요한 경우 댓글 목록과 함께 전체 댓글 수를 전달해야한다. ReplyService 인터페이스와 구현 클래스인 ReplyServiceImpl 클래스는 List<ReplyVO>를 같이 전달할 수 있는 구조로 변경한다. 

     

     

    < ReplyPageDTO 클래스 >

     

    package org.zerock.domain;
    
    import java.util.List;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.Getter;
    
    @Data
    @AllArgsConstructor
    @Getter
    public class ReplyPageDTO {
    	
    	private int replyCnt;
    	private List<ReplyVO> list;
    
    }

     

     

     

    <ReplyServiceImpl 추가>

    @Override
    public ReplyPageDTO getListPage(Criteria cri, Long bno) {
    
        return new ReplyPageDTO(
                mapper.getCountByBno(bno),
                mapper.getListWithPaging(cri, bno)
                );
    }

     

    게시글에 해당하는 댓글 수도 함께 전달하기 위해서 반환형을 ReplyPageDTO로 지정하고 return 값에 게시글 당 댓글 수를 count 할 수 있는 mapper의 쿼리문과 페이지 처리를 위한 쿼리문을 호출한다. 

     

     

     

     

    댓글 숫자 파악 - Controller

     

    <ReplyController 수정>

    //2. 특정 게시물 댓글 목록 확인
    //ReplyPageDTO로 변경
    //@PathVariable : url 호출 시 전달된 매개변수 값 page를 int형 변수 page에 저장하겠다. 
    //매핑해줄 때 value에 있는 url의 {bno}값을 전달받아 아래 service.getList의 매개변수로 전달하겠다. 
    //ResponseEntity<ReplyPageDTO>를 반환하기 때문에 return에 반환형을 맞춰줘야한다. 
    @GetMapping(value="/pages/{bno}/{page}",
            produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<ReplyPageDTO> getList(
            @PathVariable("page") int page,
            @PathVariable("bno") Long bno){
    
        Criteria cri = new Criteria(page,10); //page, amount
        log.info("get Reply List bno : " + bno);
        log.info("cri : " + cri);
        return new ResponseEntity<>(service.getListPage(cri, bno),HttpStatus.OK);
    }

     

     

     

     


     

    댓글 페이지 화면 처리

     

    댓글의 화면 처리는 아래와 같은 방법으로 처리된다.

    - 게시물을 조회하는 페이지에 들어오면, 기본적으로 가장 오래된 댓글들을 가져와서 1페이지에 보여주게 한다. 
    - 1페이지의 게시물을 갖겨올 때 해당 게시물의 댓글의 숫자를 파악해서 댓글의 페이지 번호를 출력한다.
    - 댓글이 추가되면 댓글의 숫자만을 가져와서 최종 페이지를 찾아 이동한다.
    - 댓글의 수정과 삭제 후 다시 동일 페이지를 호출한다.

     

     

     

     

    댓글 페이지 계산과 출력

     

    Ajax로 가져오는 데이터가 replyCnt와 list라는 데이터로 구성되므로 이를 처리하는 reply.js의 내용 역시 이를 처리하는 구조로 수정한다. 

     

    //댓글 페이징과 숫자 목록을 가져오게 변경
    function getList(param, callback, error) {
    
        var bno = param.bno;
        var page = param.page || 1;
        //$.getJSON: 전달받은 주소로 GET 방식의 HTTP 요청을 전송하여, 응답으로 JSON 파일을 전송받음.
        //url은 '/replies/pages/게시물번호/페이지번호' 형태이다.
        $.getJSON("/replies/pages/" + bno + "/" + page,
            function(data) {
                if (callback) {
                    //callback(data); //댓글 목록만 가져오는 경우
                    callback(data.replyCnt,data.list);//댓글 숫자와 목록을 가져오는 경우
                }
            }).fail(function(xhr, status, err) {
            if (error) {
                error();
            }
        });
    }

     

     

     

    callback 함수에 data.replyCnt와 data.list를 매개변수로 받기 때문에 get.jsp에서도 수정해줘야 한다. showList() 함수는 파라미터로 전달되는 page 변수를 이용해 원하는 댓글 페이지를 가져오게 된다. 이때 만일 page 번호가 '-1'로 전달되면 마지막 페이지를 찾아서 다시 호출하게 된다. 

     

     

    function showList(page){
    
    
        replyService.getList({bno:bnoValue,page: page|| 1 }, function(replyCnt,list) {
    
    
        console.log("replyCnt : " + replyCnt);
        console.log("list:" + list);
        console.log(list);
    
        //사용자가 댓글을 추가하면 참이되는 조건식
        if(page == -1){
            //pageNum을 Math.ceil을 이용해 무조건 올림
            //만약 댓글이 21개면 3페이지가 돼야 하기에
            pageNum = Math.ceil(replyCnt/10.0);
            showList(pageNum);
            return;
        }
    
         var str="";
    
         if(list == null || list.length == 0){
            replyUL.html("");
           return;
         }
    
         for (var i = 0, len = list.length || 0; i < len; i++) {
           str +="<li class='left clearfix' data-rno='"+list[i].rno+"'>";
           str +="  <div><div class='header'><strong class='primary-font'>["
               +list[i].rno+"] "+list[i].replyer+"</strong>"; 
           str +="    <small class='pull-right text-muted'>"+replyService.displayTime(list[i].replyDate)+"</small></div>"
           str +="    <p>"+list[i].reply+"</p></div></li>";
         }
    
         //<div>의 innerHTML로 추가한다.
         replyUL.html(str);
         showReplyPage(replyCnt);
    
       });//end function
    
     }//end showList

     

     

    사용자가 새로운 댓글을 추가하면 showList(-1);을 호출해서 우선 전체 댓글의 숫자를 파악하게 된다. 이 후에 다시 마지막 페이지를 호출해 이동시키는 방식으로 동작시킨다. 따라서 댓글 등록이라는 modalRegisterBtn.on("click", function(e){} 함수에서 showList(-1);로 설정한다. 

     

     

     

    게시글 상세보기 페이지 화면에서 댓글 창 하단 부분 footer 부분에 댓글 페이징 처리를 위한 get.jsp에 수정.

     

     

    	//댓글 페이지 하단 페이지 번호 출력
        var pageNum = 1;
        var replyPageFooter = $(".panel-footer");
        
        function showReplyPage(replyCnt){
          
          //페이지 마지막 번호는 페이지 숫자를 소숫점으로 나눈 다음 소숫점을 올려주고 10을 곱한다.
          //예를들어 3페이지인 경우 0.3을 소숫점으로 올리면 1 * 10 = 10이 되는 것.
          var endNum = Math.ceil(pageNum / 10.0) * 10;  
          var startNum = endNum - 9; 
          
          //startNum이 1이 아니면 prev는 true!
          var prev = startNum != 1;
          var next = false;
          
          //만약 endNum이 실제 댓글 수보다 크다면 endNum은 재정의!
          if(endNum * 10 >= replyCnt){
            endNum = Math.ceil(replyCnt/10.0);
          }
          
          if(endNum * 10 < replyCnt){
            next = true;
          }
          
          var str = "<ul class='pagination pull-right'>";
          
          //이전 페이지 이동
          if(prev){
            str+= "<li class='page-item'><a class='page-link' href='"+(startNum -1)+"'>Previous</a></li>";
          }
          
          //1~n 페이징 처리
          for(var i = startNum ; i <= endNum; i++){
            
        	//현재 페이지와 같다면 색을 칠해 표시해주기
            var active = pageNum == i? "active":"";
            
            str+= "<li class='page-item "+active+" '><a class='page-link' href='"+i+"'>"+i+"</a></li>";
          }
          
          //다음 페이지 이동
          if(next){
            str+= "<li class='page-item'><a class='page-link' href='"+(endNum + 1)+"'>Next</a></li>";
          }
          
          str += "</ul></div>";
          
          console.log(str);
          
          //str을 통해 html을 붙여주는 형식으로 작성
          //즉) <div>의 innerHTML으로 추가한다. 
          //이렇게 출력하려면 showList()의 마지막에 페이지 출력하도록 수정
          replyPageFooter.html(str);
        }
        
        //댓글 페이지 번호 클릭 시 해당 페이지로 이동
        replyPageFooter.on("click","li a", function(e){
           e.preventDefault();
           console.log("page click");
           
           var targetPageNum = $(this).attr("href");
           
           console.log("targetPageNum: " + targetPageNum);
           
           pageNum = targetPageNum;
           
           showList(pageNum);
         });

     

     

    페이징 번호 클릭하면 targetPage가 콘솔에 찍히는 걸 확인 가능하다.

     

     

     

    댓글의 수정과 삭제 후 이벤트 처리한 댓글이 보이도록 그 페이지로 이동하기 위해서 modalModBth,과 modalRemoveBtn 버튼을 on 클릭했을 때 실행되는 함수에 showList의 인자 값을 pageNum으로 지정한다. 

     

     

    반응형

    댓글

Designed by Tistory.