-
[게시판 만들기] 1️⃣ 설계와 CRUD 구현Spring Framework 2024. 5. 10. 18:08
https://github.com/jngsngjn/board-study
GitHub - jngsngjn/board-study: 게시판을 만들어 봅시다!
게시판을 만들어 봅시다! Contribute to jngsngjn/board-study development by creating an account on GitHub.
github.com
목표
- 게시판 CRUD 구현
- Spring Security와 함께 인증, 인가 구현
- 기본적인 검증 로직 구현
- 회원가입 시 이메일 인증 구현
개발 환경
- 인텔리제이 ultimate
- JDK 17
- Gradle
- Spring Boot
- Thymeleaf, JPA, MySql, Bean Validation, Spring Security
URL 설계
- 게시글 페이지 조회 : /borads GET
- 특정 게시글 조회 : /boards/{boardId} GET
- 게시글 작성 : /boards/write GET POST
- 게시글 수정 : /boards/{boardId}/edit GET PUT
- 게시글 삭제 : /boards/{boardId}/delete DELETE
인증, 인가
- USER, ADMIN 권한 설정
- 인증된 사용자(USER)만 글쓰기 가능
- 비인증 사용자는 글 조회만 가능
- 특정 게시글을 작성한 작성자 또는 관리자만이 글 삭제 가능
CRUD 구현하기
기본적인 게시판 CRUD 기능을 구현을 마친 후 기능을 점진적으로 발전시킬 계획이다.
1. Create
Controller
@Controller @RequestMapping("/boards") public class BoardController { private final BoardService boardService; public BoardController(BoardService boardService) { this.boardService = boardService; } // 글쓰기 페이지 조회 @GetMapping("/write") public String writeForm(Model model) { model.addAttribute("writeForm", new BoardForm()); return "writeForm"; } // 글쓰기 @PostMapping("/write") public String write(@ModelAttribute BoardForm boardForm) { boardService.writeBoard(boardForm); return "redirect:/boards"; } }
Service@Service @Transactional public class BoardService { private final BoardRepository boardRepository; public BoardService(BoardRepository boardRepository) { this.boardRepository = boardRepository; } public void writeBoard(BoardForm boardForm) { Board board = new Board(); board.setTitle(boardForm.getTitle()); board.setAuthor(boardForm.getAuthor()); board.setContent(boardForm.getContent()); boardRepository.save(board); } }
2. Read
Controller@Controller @RequestMapping("/boards") public class BoardController { private final BoardService boardService; public BoardController(BoardService boardService) { this.boardService = boardService; } // 게시판 페이지 조회 @GetMapping public String boardPage(@RequestParam(defaultValue = "1") int page, Model model) { model.addAttribute("boardList", boardService.findBoardList(page - 1)); return "board"; } // 특정 게시글 조회 @GetMapping("/{boardId}") public String boardOne(@PathVariable Long boardId, Model model) { BoardOne board = boardService.findOneBoard(boardId); if (board == null) { return "rediect:/boards"; } // 조회수 증가 boardService.viewBoardOne(boardId); model.addAttribute("board", board); return "boardOne"; } }
Service@Service @Transactional public class BoardService { private final BoardRepository boardRepository; public BoardService(BoardRepository boardRepository) { this.boardRepository = boardRepository; } // 모든 게시글 조회 public Page<BoardList> findBoardList(int page) { Pageable pageable = PageRequest.of(page, 10); return boardRepository.findAllBy(pageable); } // 특정 게시글 조회 public BoardOne findBoardOne(Long boardId) { return boardRepository.findByBoardId(boardId); } // 조회수 업데이트 public void viewBoardOne(Long boardId) { boardRepository.incrementViewCount(boardId); } }
게시판 페이지(board.html)<html lang="ko" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>게시판</title> </head> <body> <h2>게시판</h2> <hr> <form action="/boards/search" method="get"> <input type="text" name="keyword" placeholder="검색어를 입력하세요"> <button type="submit">검색</button> </form> <br> <table> <thead> <tr> <th>번호</th> <th>제목</th> <th>작성자</th> <th>작성일</th> <th>조회수</th> </tr> </thead> <tbody> <tr th:each="board : ${boardList}"> <td th:text="${board.boardId}"></td> <td> <a th:href="@{/boards/{boardId}(boardId=${board.boardId})}" th:text="${board.title}"></a> </td> <td th:text="${board.author}"></td> <td th:text="${board.createdDate}"></td> <td th:text="${board.viewCount}"></td> </tr> </tbody> </table> <br> <div> <ul> <li th:if="${boardList.hasPrevious()}"> <a th:href="@{/boards(page=${boardList.number})}">이전</a> </li> <li th:each="page : ${#numbers.sequence(1, boardList.totalPages)}" th:class="${page == boardList.number + 1} ? 'active'"> <a th:href="@{/boards(page=${page})}" th:text="${page}"></a> </li> <li th:if="${boardList.hasNext()}"> <a th:href="@{/boards(page=${boardList.number + 2})}">다음</a> </li> </ul> </div> <br> <a href="/boards/write">글쓰기</a> </body> </html>
특정 게시글(boardOne.html)<html lang="ko" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <h2 th:text="${board.title}"></h2> <p>작성자: <span th:text="${board.author}"></span></p> <p>내용: <span th:text="${board.content}"></span></p> <p>작성일: <span th:text="${board.createdDate}"></span></p> <p>수정일: <span th:text="${board.modifiedDate}"></span></p> <p>조회수: <span th:text="${board.viewCount}"></span></p> <a th:href="@{/boards/{boardId}/edit(boardId=${board.boardId})}">수정</a> <form th:action="@{/boards/{boardId}/delete(boardId=${board.boardId})}" method="post"> <input type="hidden" name="_method" value="delete" /> <button type="submit">삭제</button> </form> <a href="/boards">뒤로 가기</a> </body> </html>
3. Update
Controller@Controller @RequestMapping("/boards") public class BoardController { private final BoardService boardService; public BoardController(BoardService boardService) { this.boardService = boardService; } // 수정 페이지 조회 @GetMapping("/{boardId}/edit") public String editForm(@PathVariable Long boardId, Model model) { BoardOne board = boardService.findOneBoard(boardId); model.addAttribute("board", board); return "editForm"; } // 글 수정 @PostMapping("/{boardId}/edit") public String edit(@PathVariable Long boardId, @ModelAttribute BoardForm boardForm, RedirectAttributes redirectAttributes) { boardService.updateBoard(boardId, boardForm); redirectAttributes.addAttribute(boardId); return "redirect:/boards/{boardId}"; } }
Service@Service @Transactional public class BoardService { private final BoardRepository boardRepository; public BoardService(BoardRepository boardRepository) { this.boardRepository = boardRepository; } // 글 수정 public void updateBoard(Long boardId, BoardForm boardForm) { Board board = boardRepository.findById(boardId) .orElseThrow(() -> new RuntimeException("Board not found")); board.setTitle(boardForm.getTitle()); board.setContent(boardForm.getContent()); board.setAuthor(boardForm.getAuthor()); boardRepository.save(board); } }
4. Delete
Controller@Controller @RequestMapping("/boards") public class BoardController { private final BoardService boardService; public BoardController(BoardService boardService) { this.boardService = boardService; } // 글 삭제 @PostMapping("/{boardId}/delete") public String deleteOne(@PathVariable Long boardId) { boardService.delete(boardId); return "redirect:/boards"; } }
Service@Service @Transactional public class BoardService { private final BoardRepository boardRepository; public BoardService(BoardRepository boardRepository) { this.boardRepository = boardRepository; } // 글 삭제 public void delete(Long boardId) { boardRepository.deleteById(boardId); } }
기본적인 CRUD 작업이라 금방 구현할 것 같았지만 예상만큼 빨리 끝내지는 못했다. 페이징 기능을 처음 사용해 봐서 어색했지만 앞으로 사용할 일이 많을 것 같으니 얼른 익숙해져야겠다. URL 설계도 처음 해봐서 재미있었고 HTTP 메서드도 PUT, DELETE를 처음 사용해 봤다. 그리고 JPA 기술은 사용할 때마다 편리하고 신기함을 느끼는 것 같다. JPA 공부도 얼른 해야겠다! 이번 게시판 CRUD를 구현하면서 Controller, Service, Repository 간의 흐름에 대해 잘 정리할 수 있어서 좋았다!'Spring Framework' 카테고리의 다른 글
[Spring Security] 로그인 실패 시 계정 잠금 (0) 2024.05.16 [게시판 만들기] 5️⃣ 이메일 인증 처리 (1) 2024.05.14 [게시판 만들기] 4️⃣ AJAX를 통한 검증 처리 (1) 2024.05.13 [게시판 만들기] 3️⃣ CSS 적용하기 (1) 2024.05.13 [게시판 만들기] 2️⃣ Spring Security 적용 (0) 2024.05.12