-
[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으로 지정한다.
반응형'Back-End > Spring Legacy' 카테고리의 다른 글
[5-ch18 Spring AOP] 설정, execution, args, @Around, @before, @AfterThrowing (0) 2022.09.07 [5-ch18 Spring] AOP 개념, 용어(JoinPoint, Pointcut, Advice..) (0) 2022.09.07 [4-ch17 댓글 처리 ③] jQuery와 Ajax 처리, JavaScript 모듈화 (0) 2022.09.01 [4 -ch17 댓글 처리 ② ] 서비스 영역과 Controller 처리 (@RequestBody, @PathVariable, consumes, produces ) (0) 2022.09.01 [Spring REST] @RequestBody와 @RequestParam 차이 (0) 2022.09.01