2번째 필수강의 ch4. 5~6강 요약
URL과 URI의 차이
URL은 이미지나 텍스트 파일 리소스 경로이고, URN(Name)은 리소스에 유일한 이름을 붙인 것이다. 이 둘을 합친 것이 URI(Identifier)이다.
보통 URL은 전체 경로를 이야기하고, URI는 일부만 적을 때를 이야기한다.
작업 | URI | HTTP 메서드 | 설명 |
---|---|---|---|
읽기 | /board/read?bno=번호 | GET | 지정된 번호의 게시물 조회 |
삭제 | /board/remove | POST | 게시물 삭제 |
쓰기 | /board/write | GET | 게시물을 작성하기 위한 화면 |
/board/write | POST | 작성한 게시물 저장 | |
수정 | /board/modigy?bno=번호 | GET | 게시물을 수정하기 위한 화면 |
/board/modify | POST | 수정된 게시물을 저장 |
@GetMapping("/read")
public String read(Integer bno, Model m){
try {
BoardDto boardDto = boardService.read(bno);
m.addAttribute(boardDto);
} catch (Exception e) {
throw new RuntimeException(e);
}
return "board";
}
게시물 읽기와 수정하기는 같은 board.jsp를 사용한다. 게시물을 조회할 때는 jsp에 readonly 옵션을 주고, 수정할 때는 해당 옵션을 제거하는 식으로 만든다.
// board.jsp 일부
---
<div style="text-align:center">
<h2>게시물 읽기</h2>
<form action="" id="form">
<input type="text" name="bno" value="${boardDto.bno}" readonly="readonly">
<input type="text" name="title" value="${boardDto.title}" readonly="readonly">
<textarea name="content" id="" cols="30" rows="10" readonly="readonly">${boardDto.content}</textarea>
<button type="button" id="writeBtn" class="btn">등록</button>
<button type="button" id="modifyBtn" class="btn">수정</button>
<button type="button" id="removeBtn" class="btn">삭제</button>
<button type="button" id="listBtn" class="btn">목록</button>
</div>
게시글 목록에서 게시글을 클릭하면 게시물 번호를 함꼐 넘겨 해당 게시물을 조회하는 화면이 뜨고, 이 화면에서 게시물 수정 혹은 삭제를 할 수 있다.
또, 목록을 누르면 다시 게시글 목록으로 돌아가는데, 이 때 그냥 돌아가서는 안되고 원래 보고 있던 페이지로 돌아갈 수 있어야 한다. 즉 처음에 게시글을 조회할 때 보고 있던 페이지와 페이지 크기가 함께 전달되어야 한다.
먼저 게시글 목록에서 게시글 조회로 넘어갈 수 있도록 게시물 제목에 조회 페이지로 이동하는 링크를 걸어준다.
<th><a href="<c:url value='/board/read?bno=${boardDto.bno}&page=${page}&pageSize=${pageSize}'/>">${boardDto.title}</a></th>
다음으로, 조회 화면에서 다시 목록으로 이동할 수 있게 /list에 get 요청이 들어올 때 model에 page와 pageSize를 추가하여 보내도록 하고, /read에서도 page와 pageSize를 받도록 수정한다.
버튼을 눌렀을 때 동작을 실행시키려고 하면 js가 필요하다. 기본적인 jQuery를 사용해보자. jsp 파일 헤더에 다음을 추가해준다.
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
html을 브라우저가 전부 읽은 후 js가 작동하여 작업을 수행해야 하므로 하단에 script를 작성한다.
<script>
$(document).ready(function(){
$('#listBtn').on("click", function(){
location.href="<c:url value='/board/list'/>?page=${page}&pageSize=${pageSize}";
});
});
</script>
이 때 document는 html문서를 의미하고, html 문서가 준비되면 이 함수가 실행된다. js는 css selector를 그대로 사용하기 때문에 #으로 리스트로 돌아가는 버튼 아이디를 가져오고, 이 버튼에 클릭 이벤트가 발생하면 해당 링크로 이동하게 된다.
다음으로 게시글 삭제 기능을 만든다. 게시판 조회 화면에서 삭제 버튼을 누르면 boardController의 /remove로 해당 게시물의 번호가 POST 요청으로 넘겨지고, 해당 게시물이 삭제되도록 한다. + 작성자일때만 해당 게시글을 삭제할 수 있도록 작성자 정보도 함께 넘어가도록 해주어야 한다.
또, 게시글이 삭제되면 다시 게시글 목록으로 이동하도록 redirect 되도록 한다.
@PostMapping("/remove")
public String remove(Integer bno, Integer page, Integer pageSize, Model m, HttpSession session){
String writer = (String) session.getAttribute("id");
try {
boardService.remove(bno, writer);
} catch (Exception e) {
throw new RuntimeException(e);
}
m.addAttribute("page", page);
m.addAttribute("pageSize", pageSize);
return "redirect:/board/list";
}
model에 page와 pageSize를 추가해서 넘겨주면, redirect 할 때 알아서 쿼리스트링으로 붙는다.
삭제 버튼을 눌렀을 때 작동할 스크립트를 작성해준다. list 버튼과 비슷하지만, 이번에는 post 방식으로 요청을 날려야 하기 때문에 #form 을 받도록 한다.
추가로, 삭제 버튼을 눌렀을 때 다시 한 번 확인하도록 confirm이 뜨도록 한다.
$('#removeBtn').on("click", function(){
if(!confirm("게시글을 삭제하시겠습니까?")) return;
let form = $('#form');
form.attr("action","<c:url value='/board/remove'/>?page=${page}&pageSize=${pageSize}");
form.attr("method","post");
form.submit();
});
삭제가 잘 되었으면 삭제되었다고 창을 띄워주도록 한다.
다시 BoardController로 가서 remove의 결과를 int로 받고, 이 값이 1과 같으면 msg를 model에 첨부하여 보낸다.
jsp에서 받은 msg가 삭제 메시지일 경우 alert를 띄우도록 스크립트를 작성한다.
<script>
let msg = "${msg}"
if(msg=="del_ok") alert("성공적으로 삭제되었습니다.");
</script>
그런데 막상 실행해보니 삭제는 되는데 msg가 전달되고 있지 않았고, 쿼리 스트링으로 전달되고 있었다. GET 방식이라 model에 있는 값이 파라미터로 전달되기 때문에, ${msg}를 ${param.msg}로 값을 바꿔줬더니 정상적으로 alert가 떴다.
만약 삭제가 되지 않는 등 예외가 발생할 경우도 처리해준다. remove의 결과가 1이 나오지 않는다면 예외를 던지고, 이 때는 다른 msg를 넘겨주도록 수정한다.
@PostMapping("/remove")
public String remove(Integer bno, Integer page, Integer pageSize, Model m, HttpSession session){
String writer = (String) session.getAttribute("id");
try {
m.addAttribute("pageSize", pageSize);
m.addAttribute("page", page);
int count = boardService.remove(bno, writer);
if (count!=1)
throw new Exception("board removal error");
m.addAttribute("msg", "del_ok");
} catch (Exception e) {
e.printStackTrace();
m.addAttribute("msg", "del_err");
}
return "redirect:/board/list";
}
예외가 발생했을 경우의 스크립트도 추가해준다.
<script>
let msg = "${param.msg}"
if(msg=="del_ok") alert("성공적으로 삭제되었습니다.");
if(msg=="del_err") alert("삭제에 실패했습니다.");
</script>
브라우저를 우클릭하고 검사로 들어가서 없는 게시글 번호로 수정한 뒤 삭제 버튼을 누르면 예외가 발생한다. 삭제가 실행되지 않은 것과 삭제 실패 메시지가 뜨는 것을 확인할 수 있었다.
그런데 메시지가 뜨는 것까지는 좋지만, 새로고침을 하면 msg를 계속 받기 때문에 계속 메시지가 뜬다. 이 때 메시지를 model이 아닌 redirectAttributes에 저장하면 한 번만 나오고, 그 중에서도 addFlashAttribute를 사용하면 session에 잠깐 저장했다가 한 번 쓰고 지워버리기 때문에 세션에도 부담이 덜하다.
@PostMapping("/remove")
public String remove(Integer bno, Integer page, Integer pageSize,
Model m, HttpSession session, RedirectAttributes rattr){
...
rattr.addFlashAttribute("msg", "del_ok");
...
rattr.addFlashAttribute("msg", "del_err");
이 경우에는 jsp 문서에서 param.msg가 아닌 msg로 써도 받아올 수 있다.
다만 이렇게 일회용으로 만들면 삭제 후 다시 redirect된 게시글 목록이 전에 있던 페이지로 가지 않고 첫 페이지로 나온다.