페이지네이션 구현
● 스프링 프레임워크 자체 Page 를 이용해서 페이지 네이션을해주기위해 BoardDTO 객체의 리스트를 페이지네이션 사실상 DB쪽 계산을 해주는 부분인데 (limit) 화면에 보내는 페이징쪽은 따로 계산해서 만들어줘야해서 PagingVO가 필요함.
@GetMapping("/list")
public void list(Model model, @RequestParam(value = "pageNo", defaultValue = "0", required = false) int pageNo){
Page<BoardDTO> list = boardService.getList(pageNo);
log.info(">>> list >> {} ", list);
log.info(">>> totalCount >> {}" , list.getTotalElements()); // 전체 글 수
log.info(">>> totalPages >> {}" , list.getTotalPages()); // 전체 페이지 수 (realEndPage)
log.info(">>> pageNumber >> {}" , list.getNumber()); // 현재 페이지 번호 (pageNo)
log.info(">>> pageSize >> {}" , list.getSize()); // 페이지 사이즈 (qty)
log.info(">>> next >> {}" , list.hasNext()); // next 여부
log.info(">>> prev >> {}" , list.hasPrevious()); // prev 여부
log.info(">>> pgvo >> {}" , pgvo.toString() );
model.addAttribute("list", list);
}
● DB자원에서 Board객체의 리스트를 페이지 사이즈와 정렬을 넣어서 Page로 만들어서 boardDTO객체로 변환해서 Page 리스트안에 넣어주도록하고 리턴
페이지는 본인이 알아서 stream해줌.
@Override
public Page<BoardDTO> getList(int pageNo) {
// pageNo = 0부터 시작
// 0 => limit 0,10 , 1=> limit 10,10
Pageable pageable = PageRequest.of(pageNo,10,Sort.by("bno").descending());
Page<Board> list = boardRepository.findAll(pageable);
Page<BoardDTO> boardDTOList = list.map(b -> convertEntityToDto(b));
return boardDTOList;
}
● PagingVO 를 생성, 화면에 페이지네이션을 구성하기위해 시작페이지 끝페이지 이전,다음 등을 계산해서 제대로 화면에 나타나게 해주어야함.
@Getter
@Setter
@ToString
public class PagingVO {
private int totalPage;
private int startPage;
private int endPage;
private boolean hasPrev, hasNext;
private int pageNo;
public PagingVO(Page<BoardDTO> list, int pageNo){
this.pageNo = pageNo+1;
this.totalPage = list.getTotalPages();
this.endPage = (int)(Math.ceil(this.pageNo / 10.0)) * 10;
this.startPage = endPage - 9;
if(endPage > totalPage){
endPage = totalPage;
}
this.hasPrev = this.pageNo > 10;
this.hasNext = this.endPage < this.totalPage;
}
● 컨트롤러 부분에 PagingVO 객체 생성해서 model로 화면에 전달
@GetMapping("/list")
public void list(Model model, @RequestParam(value = "pageNo", defaultValue = "0", required = false) int pageNo){
// 화면에서 들어오는 pageNo = 1 / 0으로 처리가 되어야한다는 의미
// 화면에서 들어오는 pageNo = 2 / 1로 처리가 되야 한다는 의미
log.info(">>>> page No1 >> {} " , pageNo);
pageNo = (pageNo == 0 ? 0 : pageNo - 1);
log.info(">>>> page No2 >> {} " , pageNo);
Page<BoardDTO> list = boardService.getList(pageNo);
log.info(">>> list >> {} ", list);
log.info(">>> totalCount >> {}" , list.getTotalElements()); // 전체 글 수
log.info(">>> totalPages >> {}" , list.getTotalPages()); // 전체 페이지 수 (realEndPage)
log.info(">>> pageNumber >> {}" , list.getNumber()); // 현재 페이지 번호 (pageNo)
log.info(">>> pageSize >> {}" , list.getSize()); // 페이지 사이즈 (qty)
log.info(">>> next >> {}" , list.hasNext()); // next 여부
log.info(">>> prev >> {}" , list.hasPrevious()); // prev 여부
PagingVO pgvo = new PagingVO(list, pageNo);
log.info(">>> pgvo >> {}" , pgvo.toString() );
model.addAttribute("list", list);
model.addAttribute("pgvo", pgvo);
}
● 리스트 화면쪽에 페이지네이션 표시라인 화면 구성
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
<li class="page-item" th:if="${pgvo.hasPrev}">
<a class="page-link" th:href="@{/board/list(pageNo=${pgvo.startPage-1})}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<th:block th:each="i : ${#numbers.sequence(pgvo.startPage,pgvo.endPage)}">
<li class="page-item" th:classappend="${pgvo.pageNo eq i ? 'active' : '' }" >
<a class="page-link" th:href="@{/board/list(pageNo=${i})}" >[[${i}]]</a>
</li>
</th:block>
<li class="page-item" th:if="${pgvo.hasNext}" >
<a class="page-link" aria-label="Next" th:href="@{/board/list(pageNo=${pgvo.endPage+1})}">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
댓글 등록 구현
● 등록부분 화면 구현
<!--/* comment line */-->
<!--/* post */-->
<th:block sec:authorize="isAuthenticated()">
<div class="input-group mb-3">
<span class="input-group-text" id="cmtWriter" >NickName</span>
<input type="text" id="cmtText" class="form-control" placeholder="Add Comment" aria-label="Comment" aria-describedby="basic-addon1">
<button type="button" id="cmtAddBtn" class="btn btn-outline-success">post</button>
</div>
</th:block>
● js에서 버튼을 눌렀을때 등록이 가능하도록 구현해주어야함 ( 비동기 ) 게시글의 번호도 들고 들어가야함으로 스크립트에서 인지할 수 있도록 해주어야함 화면에 추가.
<script th:inline="javascript">
let bnoVal = [[${boardDTO.bno}]]
console.log(bnoVal);
</script>
<script th:src="@{/js/boardComment.js}"></script>
● js 구현
document.getElementById('cmtAddBtn').addEventListener('click', () => {
const cmtText = document.getElementById('cmtText');
const cmtWriter = document.getElementById('cmtWriter');
let cmtData = {
bno : bnoVal,
writer : cmtWriter.innerText,
content : cmtText.value
}
postCommentToServer(cmtData).then(result => {
if(result != "0"){
alert("댓글 등록 성공");
cmtText.value = "";
} else {
alert("댓글 등록 실패");
}
})
})
async function postCommentToServer(cmtData) {
try {
const url = "/comment/post"
const config = {
method: 'post',
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify(cmtData)
};
const resp = await fetch(url, config);
console.log(resp);
const result = await resp.text();
return result;
} catch (error) {
console.log(error);
}
}
등록버튼을 눌렀을 때 내용과 작성자의 값이 데이터로 저장되어 비동기로 서버로 보내주는 메서드에 넣을 수 있도록함.
비동기로 데이터를 서버로보내면 save로 insert해서 나온 id (cno) 값이 text형식으로 result에 리턴
result 값이 0이 아니면 성공, 0이면 실패
● 컨트롤러에서 리퀘스트바디로 데이터를 commentDTO 객체로 받아와서 구현 post 리턴은 id(cno) 값
@ResponseBody
@PostMapping("/post")
public String post(@RequestBody CommentDTO commentDTO){
Long cno = commentService.post(commentDTO);
return cno > 0 ? cno.toString() : "0";
}
● 서비스에는 변환 컨버트를 만들어주어야함 board에서 했던것과 같음.
default Comment convertDtoToEntity(CommentDTO commentDTO){
return Comment.builder()
.cno(commentDTO.getCno())
.bno(commentDTO.getBno())
.writer(commentDTO.getWriter())
.content(commentDTO.getContent())
.build();
}
default CommentDTO convertEntityToDto(Comment comment){
return CommentDTO.builder()
.cno(comment.getCno())
.bno(comment.getBno())
.writer(comment.getWriter())
.content(comment.getContent())
.regAt(comment.getRegAt())
.modAt(comment.getModAt())
.build();
}
● save로 insert해주고 Cno (id) 값을 리턴받을 수 있도록 함.
@Override
public Long post(CommentDTO commentDTO) {
return commentRepository.save(convertDtoToEntity(commentDTO)).getCno();
}
댓글 리스트 구현
● 출력라인 화면 샘플구성 지워질 내용임. 구성만
<!--/* spread */-->
<ul class="list-group list-group-flush" id="cmtListArea">
<li class="list-group-item">
<div class="ms-2 me-auto">
<div class="fw-bold">writer</div>
Content
</div>
<span class="badge text-bg-primary rounded-pill">regDate</span>
</li>
</ul>
● 컨트롤러에서 bno를 받아서 댓글객체의 리스트를 가져올 수 있게 서비스에 연결
@ResponseBody
@GetMapping("/list/{bno}")
public List<CommentDTO> list(@PathVariable("bno") long bno){
List<CommentDTO> list = commentService.getList(bno);
log.info(">>> list >> {}" , list);
return list;
}
● 댓글 id가아닌 게시글 번호로 찾아와야하므로 Repository에서 먼저 where문을 bno로 할 수 있도록 추가해주고
/* JpaRepository<entity name, id class> */
public interface CommentRepository extends JpaRepository<Comment, Long> {
List<Comment> findByBno(long bno);
}
● ServiceImpl에서 Entity Comment의 리스트를 CommentDTO 리스트로 변환해서 리턴해주어야함.
@Override
public List<CommentDTO> getList(long bno) {
List<Comment> list = commentRepository.findByBno(bno);
List<CommentDTO> dtoList = list.stream().map(c -> convertEntityToDto(c)).toList();
return dtoList;
}
댓글 리스트 더보기버튼(페이징) 구현
● js에서 page 부분을 일단 추가수정
async function getCommentFromServer(bno,page)
const resp = await fetch("/comment/list/"+ bno +"/" + page)
getCommentFromServer(bno,page)
● 컨트롤러에서 원래있던 List 로받는부분을 주석처리하고 page를 추가로 Path로 받아와서 댓글객체의 리스트를 Page로 받아주도록함
@ResponseBody
@GetMapping("/list/{bno}/{page}")
public Page<CommentDTO> list(@PathVariable("bno") long bno ,@PathVariable("page") int page){
// page = 0부터시작
// 1page => 0 / 2page => 1
page = (page == 0 ? 0 : page-1);
// List<CommentDTO> list = commentService.getList(bno);
Page<CommentDTO> list = commentService.getList(bno,page);
log.info(">>> list >> {}" , list);
return list;
}
● 서비스부분도 바꿔주고 서비스Impl 에서 bno를 받아 List로 받던걸 Pageable의 객체를 추가로받고 Page로 받을 수 있도록 함.
public interface CommentRepository extends JpaRepository<Comment, Long> {
// List<Comment> findByBno(long bno);
Page<Comment> findByBno(long bno, Pageable pageable);
}
● ServiceImpl 에서 원래있던 List로 받는부분을 주석처리하고 Page로 받게 새로 생성
Pageable객체를 먼저만들어서 사이즈와 정렬을 설정해주고 Repository에서 만들어둔 findByBno를 이용해 bno와 pageable을 받아서 Commet객체의 리스트를 Page로 받아주고 map으로 CommetDTO의 리스트로변환해서 Page로 받아 리턴
@Override
public Page<CommentDTO> getList(long bno, int page) {
Pageable pageable = PageRequest.of(page, 5, Sort.by("cno").descending());
Page<Comment> list = commentRepository.findByBno(bno, pageable);
Page<CommentDTO> dtoPageList = list.map(c -> convertEntityToDto(c));
return dtoPageList;
}
댓글 수정 / 삭제 구현
● 수정버튼을 눌렀을 때와 수정 모달창 안에 수정버튼을 눌렀을 때 두가지를 구현
if(e.target.classList.contains('mod')){
let li = e.target.closest('li');
let cmtWriter = li.querySelector('.fw-bold').innerText;
document.getElementById('cmtWriterMod').innerHTML = cmtWriter;
let cmtText = li.querySelector('.fw-bold').nextSibling;
document.getElementById('cmtTextMod').value = cmtText.nodeValue;
document.getElementById('cmtModBtn').setAttribute("data-cno", li.dataset.cno);
}
if(e.target.id == 'cmtModBtn'){
let cmtData = {
cno: e.target.dataset.cno,
content: document.getElementById('cmtTextMod').value
}
console.log(cmtData);
modifyCommentToServer(cmtData).then(result => {
if(result != '0'){
alert("댓글 수정 성공");
} else{
alert("댓글 수정 실패");
}
// 모달창 닫기
document.querySelector('.btn-close').click();
// 댓글 뿌리기
spreadComment(bnoVal);
})
}
- 수정 메서드구현 수정한 댓글의 내용과 번호로 만든 데이터를 받아서 보내 수정 할 수있도록함.
async function modifyCommentToServer(cmtData) {
try {
const url = "/comment/modify";
const config ={
method : "put",
headers : {
'Content-Type' : 'application/json; charset=utf-8'
},
body : JSON.stringify(cmtData)
};
const resp = await fetch(url,config);
const result = await resp.text();
return result;
} catch (error) {
console.log(error);
}
}
- 수정 Optional로 받아서 값이 존재하면 객체를 가져와서 DTO 객체의 수정된내용 set해줌
바뀐 내용을 update 하기위해 save 사용.
@Override
public Long modify(CommentDTO commentDTO) {
Optional<Comment> optional = commentRepository.findById(commentDTO.getCno());
if(optional.isPresent()){
Comment comment = optional.get();
comment.setContent(commentDTO.getContent());
return commentRepository.save(comment).getCno();
}
return null;
}
- 삭제 버튼을 눌렀을 때 버튼이 작동하도록 구현
if(e.target.classList.contains('del')){
let cno = e.target.closest('li').dataset.cno;
deleteCommentToServer(cno).then( result => {
if(result != '0'){
alert("삭제 성공");
spreadComment(bnoVal);
} else{
alert("삭제 실패");
}
})
}
- cno를 받아서 delete한 return값을 받는 메서드 (비동기)
async function deleteCommentToServer(cno) {
try{
const resp = await fetch("/comment/delete/"+ cno)
const result = await resp.text();
return result;
} catch(error){
console.log(error);
}
}
- 댓글 번호를 받아와서 삭제하고 삭제는 return이없으니 임의의 return값을 정해서 리턴해주도록함.
@ResponseBody
@GetMapping("/delete/{cno}")
public String delete(@PathVariable("cno") Long cno){
long cnoVal = commentService.delete(cno);
log.info(">>>> cnoVal >> {} ", cno);
return cnoVal == 0 ? "1" : "0" ;
}
- 삭제하고 삭제가 정상적으로 이루어졌는지 확인하기위해 Optional에 넣어주고 값이 남아있지않다면 0을 리턴하도록함.
@Override
public long delete(Long cno) {
commentRepository.deleteById(cno);
Optional<Comment> optionalComment = commentRepository.findById(cno);
return optionalComment.map(Comment::getCno).orElse(0L);
}
'boot_JPA 수업 정리' 카테고리의 다른 글
5일 (0) | 2024.11.26 |
---|---|
Boot_JPA 4일차 (1) | 2024.11.25 |
Boot_JPA 3일차 (0) | 2024.11.22 |
Boot_JPA 1일 (1) | 2024.11.20 |