39. 게시판 구현 : 목록 페이징하기

Feb 16, 2024
39. 게시판 구현 : 목록 페이징하기

1. 화면 확인하기

  • 한 페이지에 3개 나오게 페이징 하기
notion image
notion image
{{> layout/header}} <h1> {{#sessionUser}} {{username}} {{/sessionUser}} </h1> <div class="container p-5"> {{#boardList}} <div class="card mb-3"> <div class="card-body"> <h4 class="card-title mb-3">{{title}}</h4> <a href="/board/{{id}}" class="btn btn-primary">상세보기</a> </div> </div> {{/boardList}} <ul class="pagination d-flex justify-content-center"> <li class="page-item disabled"><a class="page-link" href="#">Previous</a></li> <li class="page-item"><a class="page-link" href="#">Next</a></li> </ul> </div> {{> layout/footer}}
 

2. BoardController에서 index() 수정하기

  • 스프링에서 쿼리 스트링 받는 방법은 매개 변수에 받기
  • @RequestParam을 사용하지 않으면 쿼리스트링을 보고 정확하게 적어야 함
  • 페이지가 null일 때 처리하기 위해 defaultValue=”0” 설정하기
package shop.mtcoding.blog.board; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import shop.mtcoding.blog.reply.ReplyRepository; import shop.mtcoding.blog.user.User; import java.util.List; @RequiredArgsConstructor @Controller public class BoardController { private final HttpSession session; // DI private final BoardRepository boardRepository; // DI private final ReplyRepository replyRepository; // ?title=제목1&content=내용1 // title=제목1&content=내용1 // 쿼리 스트링과 -x-www-form-urlencoded와 파싱 방법이 동일함 @PostMapping("/board/{id}/update") public String update(@PathVariable int id, BoardRequest.UpdateDTO requestDTO) { // 파싱 전략이 json으로 바뀜 // System.out.println(requestDTO); 정보 받기 확인 // 인증 체크하기 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) { return "redirect:/loginForm"; } // 권한 체크하기 Board board = boardRepository.findById(id); if (board.getUserId() != sessionUser.getId()) { return "error/403"; } // update board_tb set title =?, content =?, where id =? boardRepository.update(requestDTO, id); return "redirect:/board/" + id; // 수정한 게시글로 돌아가기 } @GetMapping("/board/{id}/updateForm") // 보드에 해당 페이지 public String updateFormn(@PathVariable int id, HttpServletRequest request) { // 인증 체크하기 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) { return "redirect:/loginForm"; } // 권한 체크하기 Board board = boardRepository.findById(id); if (board.getUserId() != sessionUser.getId()) { request.setAttribute("status", 403); request.setAttribute("msg", "게시글을 수정할 권한이 없습니다"); return "error/40x"; // 리다이렉트 하면 데이터 사라지니까 하면 안됨 } // 가방에 담기 request.setAttribute("board", board); return "board/updateForm"; } @PostMapping("/board/{id}/delete") // body데이터가 없어서 유효성 검사 안해도 됨 public String delete(@PathVariable int id) { // 1. 인증 검사하기 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) { return "redirect:/loginForm"; } // 2. 권한 검사하기 Board board = boardRepository.findById(id); if (board.getUserId() != sessionUser.getId()) { return "error/403"; } boardRepository.deleteById(id); return "redirect:/"; } @PostMapping("/board/save") public String save(BoardRequest.SaveDTO requestDTO, HttpServletRequest request) { // 1. 인증 체크 User sessionUser = (User) session.getAttribute("sessionUser"); System.out.println("sessionUser:" + sessionUser); if (sessionUser == null) { return "redirect:/loginForm"; } // 2. 바디 데이터 확인 및 유효성 검사 System.out.println(requestDTO); if (requestDTO.getTitle().length() > 30) { request.setAttribute("status", 400); request.setAttribute("msg", "title의 길이가 30자를 초과해서는 안되요"); return "error/40x"; // BadRequest } // 3. 모델 위임 // insert into board_tb(title, content, user_id, created_at) values(?,?,?, now()); boardRepository.save(requestDTO, sessionUser.getId()); return "redirect:/"; } // http://localhost:8080?page=0 쿼리스트링 // 스프링에서 쿼리 스트링 받는 방법은 매개 변수에 받기 @GetMapping({"/"}) public String index(HttpServletRequest request, @RequestParam(value = "page", defaultValue = "0") Integer page) { List<Board> boardList = boardRepository.findAll(page); request.setAttribute("boardList", boardList); return "index"; } @GetMapping("/board/saveForm") // /board/saveForm Get요청이 옴 public String saveForm() { // session 영역에 접근하기 위한 // 1. session 영역에 sessionUser 키 값에 user 객체가 있는지 체크하기 User sessionUser = (User) session.getAttribute("sessionUser"); // 2. 값이 null이면 로그인 페이지로 리다이렉션 if (sessionUser == null) { return "redirect:/loginForm"; } // 3. null이 아니면 /board/saveForm으로 이동 return "board/saveForm"; } // 상세보기시 호출 @GetMapping("/board/{id}") // 1이 프라이머리키 -> 뭐든 넣어도 실행시키려면 변수화시켜서 {} public String detail(@PathVariable int id, HttpServletRequest request) { User sessionUser = (User) session.getAttribute("sessionUser"); // 페이지 권한 BoardResponse.DetailDTO boardDTO = boardRepository.findByIdWithUser(id); //메서드 이름 변경 boardDTO.isBoardOwner(sessionUser); // null이면 터짐 List<BoardResponse.ReplyDTO> replyDTOList = replyRepository.findByBoardId(id, sessionUser); request.setAttribute("board", boardDTO); request.setAttribute("replyList", replyDTOList); return "board/detail"; } }
 

3. limit 확인하기

select * from board_tb order by id desc; select * from board_tb order by id desc limit 0,3; select * from board_tb order by id desc limit 1,3; select * from board_tb order by id desc limit 3,1; select * from board_tb order by id desc limit 1,2;
notion image
 

4. BoardRepository에서 findAll() 수정하기

  • defaultValue가 잡혀있어서 null일 수 없음
  • index가 3씩 나와야 하니까 *3 하기
package shop.mtcoding.blog.board; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import java.util.List; @RequiredArgsConstructor @Repository public class BoardRepository { private final EntityManager em; public List<Board> findAll(Integer page) { Query query = em.createNativeQuery("select * from board_tb order by id desc limit ?,3", Board.class); query.setParameter(1, page*3); // index가 3씩 나와야 함 return query.getResultList(); } public Board findById(int id) { Query query = em.createNativeQuery("select * from board_tb where id = ?", Board.class); query.setParameter(1, id); Board board = (Board) query.getSingleResult(); return board; } public BoardResponse.DetailDTO findByIdWithUser(int idx) { Query query = em.createNativeQuery("select b.id, b.title, b.content, b.user_id, u.username from board_tb b inner join user_tb u on b.user_id = u.id where b.id = ?"); query.setParameter(1, idx); Object[] row = (Object[]) query.getSingleResult(); Integer id = (Integer) row[0]; String title = (String) row[1]; String content = (String) row[2]; int userId = (Integer) row[3]; String username = (String) row[4]; System.out.println("id : " + id); System.out.println("title : " + title); System.out.println("content : " + content); System.out.println("userId : " + userId); System.out.println("username : " + username); BoardResponse.DetailDTO responseDTO = new BoardResponse.DetailDTO(); responseDTO.setId(id); responseDTO.setTitle(title); responseDTO.setContent(content); responseDTO.setUserId(userId); responseDTO.setUsername(username); return responseDTO; } @Transactional public void save(BoardRequest.SaveDTO requestDTO, int userId) { Query query = em.createNativeQuery("insert into board_tb(title, content, user_id, created_at) values(?,?,?, now())"); query.setParameter(1, requestDTO.getTitle()); query.setParameter(2, requestDTO.getContent()); query.setParameter(3, userId); query.executeUpdate(); } @Transactional public void deleteById(int id) { Query query = em.createNativeQuery("delete from board_tb where id = ?"); query.setParameter(1, id); query.executeUpdate(); } @Transactional public void update(BoardRequest.UpdateDTO requestDTO, int id) { Query query = em.createNativeQuery("update board_tb set title=?, content=? where id = ?"); query.setParameter(1, requestDTO.getTitle()); query.setParameter(2, requestDTO.getContent()); query.setParameter(3, id); query.executeUpdate(); } }
 

5. 필요한 것 추가하기

 

6. BoardRepository에 count() 구현하기

  • 전체 페이지 확인하기
  • 현재 페이지가 첫 페이지인지 마지막 페이지인지 확인하기 위함
  • 첫 페이지는 =0이기 때문에 마지막 페이지 때문에 하는 것
  • 마지막 페이지는 현재 페이지와 전체 페이지를 알아야 함
  • 원래는 boardList에 한번에 담아서 가져오는 것이 맞음
모든 view로 보내는 ENTITY는 다 DTO로 바꿔서 보내야
package shop.mtcoding.blog.board; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import shop.mtcoding.blog.reply.ReplyRepository; import shop.mtcoding.blog.user.User; import java.util.List; @RequiredArgsConstructor @Controller public class BoardController { private final HttpSession session; // DI private final BoardRepository boardRepository; // DI private final ReplyRepository replyRepository; // ?title=제목1&content=내용1 // title=제목1&content=내용1 // 쿼리 스트링과 -x-www-form-urlencoded와 파싱 방법이 동일함 @PostMapping("/board/{id}/update") public String update(@PathVariable int id, BoardRequest.UpdateDTO requestDTO) { // 파싱 전략이 json으로 바뀜 // System.out.println(requestDTO); 정보 받기 확인 // 인증 체크하기 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) { return "redirect:/loginForm"; } // 권한 체크하기 Board board = boardRepository.findById(id); if (board.getUserId() != sessionUser.getId()) { return "error/403"; } // update board_tb set title =?, content =?, where id =? boardRepository.update(requestDTO, id); return "redirect:/board/" + id; // 수정한 게시글로 돌아가기 } @GetMapping("/board/{id}/updateForm") // 보드에 해당 페이지 public String updateFormn(@PathVariable int id, HttpServletRequest request) { // 인증 체크하기 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) { return "redirect:/loginForm"; } // 권한 체크하기 Board board = boardRepository.findById(id); if (board.getUserId() != sessionUser.getId()) { request.setAttribute("status", 403); request.setAttribute("msg", "게시글을 수정할 권한이 없습니다"); return "error/40x"; // 리다이렉트 하면 데이터 사라지니까 하면 안됨 } // 가방에 담기 request.setAttribute("board", board); return "board/updateForm"; } @PostMapping("/board/{id}/delete") // body데이터가 없어서 유효성 검사 안해도 됨 public String delete(@PathVariable int id) { // 1. 인증 검사하기 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) { return "redirect:/loginForm"; } // 2. 권한 검사하기 Board board = boardRepository.findById(id); if (board.getUserId() != sessionUser.getId()) { return "error/403"; } boardRepository.deleteById(id); return "redirect:/"; } @PostMapping("/board/save") public String save(BoardRequest.SaveDTO requestDTO, HttpServletRequest request) { // 1. 인증 체크 User sessionUser = (User) session.getAttribute("sessionUser"); System.out.println("sessionUser:" + sessionUser); if (sessionUser == null) { return "redirect:/loginForm"; } // 2. 바디 데이터 확인 및 유효성 검사 System.out.println(requestDTO); if (requestDTO.getTitle().length() > 30) { request.setAttribute("status", 400); request.setAttribute("msg", "title의 길이가 30자를 초과해서는 안되요"); return "error/40x"; // BadRequest } // 3. 모델 위임 // insert into board_tb(title, content, user_id, created_at) values(?,?,?, now()); boardRepository.save(requestDTO, sessionUser.getId()); return "redirect:/"; } // http://localhost:8080?page=0 쿼리스트링 // 스프링에서 쿼리 스트링 받는 방법은 매개 변수에 받기 @GetMapping({"/"}) public String index(HttpServletRequest request, @RequestParam(value = "page", defaultValue = "0") Integer page) { List<Board> boardList = boardRepository.findAll(page); // 전체 페이지 갯수 Integer count = boardRepository.count().intValue(); // 5 -> 2 page // 6 -> 2 page // 7 -> 3 page // 8 -> 3 page int rest= count % 3 == 0 ? 0 : 1; int allPageCount = count/3 + rest; request.setAttribute("boardList", boardList); request.setAttribute("first", page==0); request.setAttribute("last", allPageCount == page+1); // 현재 페이지와 전체 페이지를 알아야 함 request.setAttribute("prev", page+1); request.setAttribute("next", page-1); return "index"; } @GetMapping("/board/saveForm") // /board/saveForm Get요청이 옴 public String saveForm() { // session 영역에 접근하기 위한 // 1. session 영역에 sessionUser 키 값에 user 객체가 있는지 체크하기 User sessionUser = (User) session.getAttribute("sessionUser"); // 2. 값이 null이면 로그인 페이지로 리다이렉션 if (sessionUser == null) { return "redirect:/loginForm"; } // 3. null이 아니면 /board/saveForm으로 이동 return "board/saveForm"; } // 상세보기시 호출 @GetMapping("/board/{id}") // 1이 프라이머리키 -> 뭐든 넣어도 실행시키려면 변수화시켜서 {} public String detail(@PathVariable int id, HttpServletRequest request) { User sessionUser = (User) session.getAttribute("sessionUser"); // 페이지 권한 BoardResponse.DetailDTO boardDTO = boardRepository.findByIdWithUser(id); //메서드 이름 변경 boardDTO.isBoardOwner(sessionUser); // null이면 터짐 List<BoardResponse.ReplyDTO> replyDTOList = replyRepository.findByBoardId(id, sessionUser); request.setAttribute("board", boardDTO); request.setAttribute("replyList", replyDTOList); return "board/detail"; } }
 

7. index.mustache에서 수정하기

{{> layout/header}} <h1> {{#sessionUser}} {{username}} {{/sessionUser}} </h1> <div class="container p-5"> {{#boardList}} <div class="card mb-3"> <div class="card-body"> <h4 class="card-title mb-3">{{title}}</h4> <a href="/board/{{id}}" class="btn btn-primary">상세보기</a> </div> </div> {{/boardList}} <ul class="pagination d-flex justify-content-center"> <li class="page-item "><a class="page-link" href="?page={{prev}}">Previous</a></li> <li class="page-item {{#last}}disabled{{/last}}"><a class="page-link" href="?page={{next}}">Next</a></li> </ul> </div> {{> layout/footer}}
notion image
notion image
Share article

vosw1