어제 모달 댓글 처리 하던 거 버그 수정
// 댓글목록 모달팝업 이벤트
$(".chat").on("click", "li", function() {
var rno = $(this).data("rno");
console.log(rno);
replyService.get(rno, function(result) {
console.log(result);
$("#reply").val(result.reply);
$("#replyer").val(result.replyer);
$("#replyDate").val(replyService.displayTime(result.replyDate)).prop("readonly", true).closest("div").show();
$("#myModal").data("rno", rno)
$(".ac").show().eq(2).hide();
$("#myModal").modal("show");
});
})
🔶 댓글 등록 모달을 한 번 열고 닫은 후에 기존 댓글의 모달 창을 열면, 댓글 등록일 폼이 사라지는 현상이 생겨서 8번째 줄처럼 수정해 줌.
- 댓글의 수정/삭제 이벤트 처리
get.jsp 일부
$(function() {
// 수정 적용버튼 이벤트
$("#modalModBtn").click(function() {
var reply = {rno: $("#myModal").data("rno"), reply:$("#reply").val()};
replyService.update(reply, function(result) {
alert(result);
$("#myModal").modal("hide");
showList(pageNum);
});
});
// 삭제 적용버튼 이벤트
$("#modalRemoveBtn").click(function() {
replyService.remove($("#myModal").data("rno"), function(result) {
alert(result);
$("#myModal").modal("hide");
showList(pageNum);
});
});
});
🔶 .data()는 게터같은 느낌. 댓글 목록 모달 팝업 이벤트 쪽에서 $("#myModal").data("rno", rno)
를 해줬기 때문에 쓸 수 있는 것.
17.6 댓글의 페이징 처리
- 데이터베이스의 인덱스 설계
TBL_REPLY에 접근할 때 댓글 번호(rno)가 중심이 아니라, 게시물의 번호(bno)가 중심이 되어야 함.
효율을 높이려면 게시물의 번호에 맞게 댓글들을 모아서 빠르게 찾을 수 있는 구조로 만들어야 한다.
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 = 163878
AND RNO > 0;
페이징 처리를 위한 쿼리는
SELECT * FROM (
SELECT /*+INDEX(TBL_REPLY IDX_REPLY) */
ROWNUM RN, BNO, RNO, REPLY, REPLYER, REPLYDATE, UPDATEDATE
FROM TBL_REPLY
WHERE BNO = 163878
AND RNO > 0
AND ROWNUM <= 20
)
WHERE RN > 10;
🔶 한 페이지당 10개의 댓글이 있고, 이 쿼리문은 2페이지를 나타낸다.
ReplyMapper.xml 일부
<select id="getListWithPaging" resultType="site.levinni.domain.ReplyVO">
SELECT * FROM (
SELECT /*+INDEX(TBL_REPLY IDX_REPLY) */
ROWNUM RN, BNO, RNO, 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>
🔶 단순히 pageNum이 아니라, cri.pageNum, cri.amount
ReplyMapperTest.java를 한 결과
❓ rn이 unread인 이유
다른 열들은 ReplyVO라는 타입으로 줘서 각 값이 바인딩 된 건데 rn은 select 시점에 준 거고 ReplyVO에 없기 때문에 unread라고 나오는 것.
- 댓글의 숫자 파악
ReplyMapper.java 일부
int getTotalCount(@Param("cri") Criteria cri, @Param("bno") Long bno);
ReplyMapper.xml 일부
<select id="getTotalCount" resultType="int">
<![CDATA[
SELECT count(*) FROM TBL_REPLY WHERE BNO = #{bno}
]]>
</select>
- ReplyServiceImpl에서 댓글과 댓글 수 처리
ReplyPageDTO.java
package site.levinni.domain;
import java.util.List;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class ReplyPageDTO extends PageDTO{
private List<ReplyVO> list;
private int lastPage;
public ReplyPageDTO(Criteria cri, int total) {
super(cri, total);
}
public ReplyPageDTO(Criteria cri, int total, List<ReplyVO> list) {
super(cri, total);
this.list = list;
}
}
ReplyServiceImpl.java 일부
@Override
public ReplyPageDTO getListPage(Criteria cri, Long bno) {
// TODO Auto-generated method stub
return new ReplyPageDTO(cri,
replymapper.getTotalCount(cri, bno),
replymapper.getListWithPaging(cri, bno));
}
ReplyServiceTests.java 일부
@Test
public void testGetListPage() {
ReplyPageDTO dto = service.getListPage(new Criteria(11,10), 163878L);
log.info(dto);
log.info(dto.getCri());
log.info(dto.getStartPage());
log.info(dto.getTotal());
log.info(dto.isPrev());
log.info(dto.isNext());
dto.getList().forEach(log::info);
}
🔶 현재 11페이지의 목록을 나타낸 거니깐 1~10페이지를 볼 수 있는 prev 버튼이 활성화되므로 isPrev는 true, 댓글 개수가 많지 않아 21페이지는 없기 때문에 isNext는 false다.
- ReplyController 수정
@GetMapping("pages/{page}/{bno}")
public ResponseEntity<ReplyPageDTO> getList(@PathVariable int page, @PathVariable long bno) {
log.info("getList");
Criteria cri = new Criteria(page, 10);
return new ResponseEntity<>(service.getListPage(cri, bno), HttpStatus.OK);
}
🔶 service.getListPage를 가져오고 ResponseEntity의 제네릭은 ReplyPageDTO가 된다.
17.7 댓글 페이지의 화면 처리
- 게시물 조회 페이지에서 가장 오래된 댓글들을 가져와서 1페이지에 보여 주기.
- 해당 게시물의 댓글 숫자를 파악해서 댓글의 페이지 번호를 출력하기.
- 댓글이 추가되면 댓글의 숫자만을 가져와서 최종 페이지로 이동하기.
- 댓글의 수정과 삭제 후에는 동일 페이지를 호출하기.
위까지 적용하고 나면 게시물에서 댓글 목록 페이지는 아래 사진처럼 되어있다.
댓글 내용뿐만 아니라 댓글 수도 정상 출력이 되지 않는데, 7개로 나오는 이유는
표시 부분이 총 7개이기 때문이다.
- 댓글 페이지 계산과 출력
PageDTO.java
package site.levinni.domain;
import lombok.Data;
@Data
public class PageDTO {
private int startPage;
private int endPage;
private int realEnd; // 추가 부분
private boolean prev;
private boolean next;
private int total;
private Criteria cri;
public PageDTO(Criteria cri, int total) {
this.cri = cri;
this.total = total;
endPage = (cri.getPageNum() + 9) / 10 * 10;
startPage = endPage - 9;
realEnd = (total + 9) / 10; // 댓글 총 개수의 진짜 마지막 페이지
endPage = realEnd < endPage ? realEnd : endPage;
prev = startPage > 1;
next = endPage < realEnd;
}
}
get.jsp 일부
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<script>
$(function() {
function showList(page) {
replyService.getList({bno:bno, page:page}, function(result) {
// 댓글 목록 출력
var list = result.list;
console.log(result);
console.log(list);
// 댓글 썼을 때 마지막 페이지 봐야 함.
if(page == -1) showList(result.realEnd);
var str = "";
replyUL.html("");
for(var i in list) {
str += '<li class="list-group-item" data-rno="' + list[i].rno + '">';
str += ' <div class="header">';
str += ' <strong>' + list[i].replyer + '</strong>';
str += ' <small class="float-right">' + replyService.displayTime(list[i].replyDate) + '</small>';
str += ' </div>';
str += ' <p class="mt-2">' + list[i].reply + '</p>';
str += '</li>';
}
replyUL.html(str);
// 댓글 페이징
str = '<ul class="pagination float-right mt-4">';
if(result.prev) {
str += '<li class="paginate_button page-item previous">';
str += ' <a class="page-link" href="' + (result.startPage - 1) + '">Prev</a>';
str += '</li>';
}
for(var i = result.startPage ; i <= result.endPage ; i++) {
var active = result.cri.pageNum == i ? "active" : "";
str += '<li class="paginate_button page-item ' + active + '">';
str += ' <a class="page-link" href="' + i + '">' + i + '</a>';
str += '</li>';
}
if(result.next) {
str += '<li class="paginate_button page-item next">';
str += ' <a class="page-link" href="' + (result.endPage + 1) + '">Next</a>';
str += '</li>';
}
str += '</ul>';
$(".reply-footer").html(str);
})
}
// 등록 적용버튼 이벤트
$("#modalRegBtn").click(function() {
var reply = {bno:bno, reply:$("#reply").val(), replyer:$("#replyer").val()};
replyService.add(reply, function(result) {
alert(result);
$("#myModal").modal("hide");
showList(-1);
});
});
}
</script>
🔶 새로운 댓글을 등록하면 showList(-1);을 호출해서 우선 전체 댓글의 숫자를 파악하고 그 후에 마지막 페이지를 호출해서 이동시키는 방식.
🔶 동적 태그 처리에서 if문은 prev페이지가 존재하면 next페이지가 존재하면 해당 버튼을 나타낸다는 것이다.
<script>
var pageNum = 1;
// 댓글 페이지 버튼 이벤트
$(".reply-footer").on("click", "a", function() {
event.preventDefault();
pageNum = $(this).attr("href");
showList(pageNum);
});
</script>
etc. 댓글 더보기 처리
SELECT /*+INDEX(TBL_REPLY IDX_REPLY) */
BNO, RNO, REPLY, REPLYER, REPLYDATE, UPDATEDATE
FROM TBL_REPLY
WHERE BNO = 163938
AND ROWNUM <= 10
AND RNO > 162;
🔶 페이징 처리와 다르게 파라미터로 page번호가 필요없고, 대신 rno가 추가 된다.
ReplyMapper.java 일부
List<ReplyVO> getListWithShowMore(@Param("rno") Long rno, @Param("bno") Long bno);
ReplyMapper.xml 일부
<select id="getListWithShowMore" resultType="site.levinni.domain.ReplyVO">
SELECT /*+INDEX(TBL_REPLY IDX_REPLY) */
BNO, RNO, REPLY, REPLYER, REPLYDATE, UPDATEDATE
FROM TBL_REPLY
WHERE BNO = #{bno}
AND ROWNUM <= 10
<if test="rno != null">
AND RNO > #{rno}
</if>
</select>
ReplyServiceImpl.java 일부
@Override
public List<ReplyVO> getListMore(Long rno, Long bno) {
// TODO Auto-generated method stub
return replymapper.getListWithShowMore(rno, bno);
}
ReplyController.java 일부
// 페이지(더보기)
@GetMapping({"more/{bno}", "more/{bno}/{rno}"})
public ResponseEntity<List<ReplyVO>> getListMore(@PathVariable Long bno, @PathVariable Optional<Long> rno) {
log.info("getListMore");
return new ResponseEntity<>(service.getListMore(rno.isPresent() ? rno.get(): null , bno), HttpStatus.OK);
}
🔶 rno를 생략하면 첫 댓글 부분 나옴.
👀 오류 날적이
💥 댓글 번호(rno)가 언디파인드.
👍 해결법
이 값이 바인딩 되지 않아서 언디파인드인 건데,
get.jsp에서 태그를 동적 처리 할 때, 해당 부분을 썼는지, 오탈자 확인해 보기.
💥 페이지 네이션에 현재 페이지에 active 클래스가 적용 안 되는 문제.
👍 부트스트랩에서 클래스는 공백(띄어쓰기)로 구분하기 때문에, 동적 태그 처리 시
page-item 뒤에 공백 하나를 줘야 한다! 안 그러면 page-itemactive라는 클래스로 인식해 버림. 브라우저 f12에서 Element 검사로 찾아냈다. 😂
'스프링 Spring' 카테고리의 다른 글
Part 5. AOP와 트랜잭션 21. 04. 14. (0) | 2021.04.15 |
---|---|
댓글 더보기 처리 이어서 21. 04. 14. (0) | 2021.04.15 |
21. 04. 12. (0) | 2021.04.13 |
21. 04. 07. (0) | 2021.04.08 |
21. 04. 06. (0) | 2021.04.06 |