본문 바로가기

Heute lerne ich/Java

[스프링부트 게시판 프로젝트] 목록/조회/수정/삭제

01. 목록

- list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>list</title>
</head>
<body>
<table>
    <tr>
        <th>id</th>
        <th>title</th>
        <th>title(||쓰지 않은 경우)</th>
        <th>writer</th>
        <th>date</th>
        <th>hits</th>
    </tr>
    <tr th:each="board: ${boardList}">
        <td th:text="${board.id}"></td>
        <td><a th:href="@{|/board/${board.id}|}" th:text="${board.boardTitle}"></a></td>
        <td><a th:href="@{/board/${board.id}}" th:text="${board.boardTitle}"></a></td>
        <td th:text="${board.boardWriter}"></td>
        <td th:text="*{#temporals.format(board.boardCreatedTime, 'yyyy-MM-dd HH:mm:ss')}"></td>
        <td th:text="${board.boardHits}"></td>
    </tr>
</table>
</body>
</html>

 

href에 |를 쓰지 않은 경우 클릭하면 id값이 제대로 전달되지 않는다.

 

 

- BoardController

GetMapping("/")
public String findAll(Model model){
    // DB에서 전체 게시글 데이터를 가져와서 list.html에 보여준다.
    List<BoardDTO> boardDTOList = boardService.findAll();
    model.addAttribute("boardList", boardDTOList);
    return "list";
}

 

boardService의 findAll() 메서드를 불러온다.

 

 

- BoardService

public List<BoardDTO> findAll() {
    // 리스트 형태의 entity가 넘어옴
    List<BoardEntity> boardEntityList = boardRepository.findAll();
    // 이 객체를 dto 객체로 옮겨 담아야 함
    List<BoardDTO> boardDTOList = new ArrayList<>();
    for (BoardEntity boardEntity : boardEntityList){
        boardDTOList.add(BoardDTO.toBoardDTO(boardEntity));
    }
    return boardDTOList;
}

 

리스트 형태의 entity가 boardEntityList에 담긴다.

따라서 이를 dto 객체로 옮겨 담기 위해, 반복문을 이용하여 각각의 entity를 BoarDTO의 toBoardDTO 메서드를 적용한다.

 

 

- BoardRepository

public static BoardDTO toBoardDTO(BoardEntity boardEntity){
    BoardDTO boardDTO = new BoardDTO();
    boardDTO.setBoardContents(boardEntity.getBoardContents());
    boardDTO.setBoardPass(boardEntity.getBoardPass());
    boardDTO.setBoardWriter(boardEntity.getBoardWriter());
    boardDTO.setBoardTitle(boardEntity.getBoardTitle());
    boardDTO.setBoardCreatedTime(boardEntity.getCreatedTime());
    boardDTO.setBoardUpdatedTime(boardEntity.getUpdatedTime());
    boardDTO.setBoardHits(boardEntity.getBoardHits());
    boardDTO.setId(boardEntity.getId());
    return boardDTO;
}

 

복잡해보이지만, 간단하게 Entity를 받아 DTO로 변환하는 함수이다.

boardEntity의 값들을 get해오고, 새로 선언된 BoardDTO의 값으로 set한다.

 

 

- 결과

생성한 게시글 목록을 확인할 수 있다.

 

이때 경로에 I를 쓴 경우(왼쪽), title222를 클릭하면 /board/id로 이동한다.

 

경로 |를 쓰지 않은 경우, title222를 클릭하면 /board/id로 이동하지 않는다.

-> 경로에 | 를 작성하여 id 값을 받아야 한다.

 


02. 조회

- detail.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>detail</title>
    <!-- jquery cdn -->
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"
            integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
            crossorigin="anonymous"></script>
</head>
<body>
<table>
    <tr>
        <th>id</th>
        <td th:text="${board.id}"></td>
    </tr>
    <tr>
        <th>title</th>
        <td th:text="${board.boardTitle}"></td>
    </tr>
    <tr>
        <th>writer</th>
        <td th:text="${board.boardWriter}"></td>
    </tr>
    <tr>
        <th>date</th>
        <td th:text="${board.boardCreatedTime}"></td>
    </tr>
    <tr>
        <th>hits</th>
        <td th:text="${board.boardHits}"></td>
    </tr>
    <tr>
        <th>contents</th>
        <td th:text="${board.boardContents}"></td>
    </tr>
</table>
<button onclick="listReq()">목록</button>
<button onclick="updateReq()">수정</button>
<button onclick="deleteReq()">삭제</button>


</body>
<script th:inline="javascript">
    const listReq = () => {
        console.log("목록 요청");
        const page = [[${page}]];
        location.href = "/board/";
    }
    const updateReq = () => {
        console.log("수정 요청");
        const id = [[${board.id}]];
        location.href = "/board/update/" + id;
    }
    const deleteReq = () => {
        console.log("삭제 요청");
        const id = [[${board.id}]];
        location.href = "/board/delete/" + id;
    }
</script>
</html>

 

목록, 수정, 삭제 버튼을 만들고 각각의 링크로 이동할 수 있도록 만든다.

 

 

- BoardController

@GetMapping("/{id}")
public String findById(@PathVariable Long id, Model model){
    // 해당 게시글의 조회수를 하나 올리고
    // 게시글 데이터를 가져와서 detail.html에 출력
    boardService.updateHits(id);
    BoardDTO boardDTO = boardService.findById(id);
    model.addAttribute("board", boardDTO);
    return "detail";
}

 

조회시 해당 게시글의 조회수가 하나 증가하고, 게시글 데이터를 가져와야 한다.

따라서 boardService의 updateHits(id)를 호출하고, findById로 id를 가진 게시글의 데이터를 불러온다.

 

 

- BoardService의 findById

public BoardDTO findById(Long id) {
    Optional<BoardEntity> optionalBoardEntity = boardRepository.findById(id);
    if (optionalBoardEntity.isPresent()) {
        BoardEntity boardEntity = optionalBoardEntity.get();
        return BoardDTO.toBoardDTO(boardEntity);
    }else{
        return null;
    }
}

 

BoardRepository의 findById를 이용해서 값이 존재하면 boardEntity에 값을 저장하고, 이를 DTO로 변환해서 리턴한다.

값이 존재하지 않으면 null을 리턴한다.

 

 

- BoardSerivce의 updateHits

@Transactional
public void updateHits(Long id) {
    boardRepository.updateHits(id);
}

 

boardRespository의 updateHits(id)를 호출한다.

findById와 달리 update 쿼리는 직접 작성해주어야 한다.

 

 

- BoardRepository

public interface BoardRepository extends JpaRepository<BoardEntity, Long> {
    //  update board_table set board_hits=board_hists+1 where id =?
    @Modifying
    @Query(value = "update BoardEntity b set b.boardHits=b.boardHits+1 where b.id=:id")
    void updateHits(@Param("id") Long id);
}

 

해당 id를 가진 board 데이터의 board_hit 수를 전보다 하나씩 늘리는 쿼리를 작성한다.

이때 @Modifying을 추가하여 해당 메서드가 UPDATE 쿼리를 실행하고, 데이터베이스의 상태를 변경한다는 것을 명시해준다.

 

 

- 결과

상세 정보가 조회된다.

 


03. 수정

- update.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>update</title>
</head>
<body>
<form action="/board/update" method="post" name="updateForm">
    <input type="hidden" name="id" th:value="${boardUpdate.id}">
    writer: <input type="text" name="boardWriter" th:value="${boardUpdate.boardWriter}" readonly> <br>
    pass: <input type="text" name="boardPass" id="boardPass"> <br>
    title: <input type="text" name="boardTitle" th:value="${boardUpdate.boardTitle}"> <br>
    contents: <textarea name="boardContents" cols="30" rows="10" th:text="${boardUpdate.boardContents}"></textarea> <br>
    <input type="hidden" name="boardHits" th:value="${boardUpdate.boardHits}">
    <input type="button" value="글수정" onclick="boardUpdate()">
</form>
<script th:inline="javascript">
    const boardUpdate = () => {
        const pass = [[${boardUpdate.boardPass}]];
        const inputPass = document.getElementById("boardPass").value;
        if (pass == inputPass) {
            document.updateForm.submit();
        } else {
            alert("비밀번호가 일치하지 않습니다!");
        }
    }
</script>
</body>

 

수정 버튼 클릭시 나오는 페이지를 만든다.

 

 

- BoardController

// 상세 화면에서 수정 버튼 클릭
// 서버에서 해당 게시글의 정보를 가지고 수정 화면 출력
// 제목, 내용 수정 입력 받아서 서버로 요청
// 수정 처리
@GetMapping("/update/{id}")
public String updateForm(@PathVariable Long id, Model model){
    BoardDTO boardDTO = boardService.findById(id);
    model.addAttribute("boardUpdate", boardDTO);
    return "update";
}

@PostMapping("/update")
public String update(@ModelAttribute BoardDTO boardDTO, Model model){
    BoardDTO board = boardService.update(boardDTO);
    model.addAttribute("board", board);
    return "detail";
    // return "redirect:/board:/" + boardDTO.getId(); 조회수 증가의 문제 때문에 사용X
}

 

수정 버튼을 누르면 나오는 get과, 수정 완료 버튼을 누르면 요청하는 post를 만든다.

첫번째 메서드는 조회수 증가를 제외하면 상세 조회와 동일한 로직이다.

두번째 메서드는 수정 요청이므로, boardService의 update 메서드를 호출한다.

이때 redirect로 상세 조회 페이지를 호출할 수 있으나, 수정만 했는데 조회수가 증가하는 오류가 발생하므로 detail 페이지를 리턴한다.

 

 

- BoardService

public BoardDTO update(BoardDTO boardDTO) {
    BoardEntity boardEntity = BoardEntity.toUpdateEntity(boardDTO);
    boardRepository.save(boardEntity);
    return findById(boardDTO.getId());
}

 

BoardEntity의 toUpdateEntity를 통해 DTO를 Entity변환해준다.

boardRepository의 save를 통해 boardEntity를 업데이트한다.

save는 pk가 없을 때 생성, 있을 때는 업데이트를 한다. 따라서 pk가 이미 데이터베이스에 존재하는 entity를 업데이트한다.

DTO의 id만을 리턴한다.

 

 

- BoardEntity

public static BoardEntity toUpdateEntity(BoardDTO boardDTO) {
    BoardEntity boardEntity = new BoardEntity();
    boardEntity.setId(boardDTO.getId());
    boardEntity.setBoardWriter(boardDTO.getBoardWriter());
    boardEntity.setBoardPass(boardDTO.getBoardPass());
    boardEntity.setBoardContents(boardDTO.getBoardContents());
    boardEntity.setBoardTitle(boardDTO.getBoardTitle());
    boardEntity.setBoardHits(boardDTO.getBoardHits());
    return boardEntity;

 

toSaveEntity와 달리 id를 생성해주지 않고, dto의 getId를 통해 id를 가져온다.

나머지는 동일하다.

 

 

- 결과

수정 버튼을 누르면 나오는 화면이다.

 

 

이때 pass를 입력했던 비밀번호와 다르게 입력하면 일치하지 않는다는 오류가 뜨며 수정이 취소된다.

 

 

올바른 비밀번호를 입력하고 글 수정 버튼을 클릭하면, 

detail 창이 다시 보이며, 정보가 수정됨을 확인할 수 있다. (title222 -> newtitle)


04. 삭제

- BoardController

@GetMapping("/delete/{id}")
public String delete(@PathVariable Long id){
    boardService.delete(id);
    return "redirect:/board/";
}

 

boardService의 delete 메서드를 사용하고, 게시판 목록 페이지로 redirect 한다.

 

 

- BoardService

public void delete(Long id) {
    boardRepository.deleteById(id);
}

 

boardRepository의 deleteById를 사용한다. 이때 Repository에서 따로 선언해주지 않아도 자동으로 쿼리를 생성한다.

 

 

- 결과

삭제 버튼을 클릭하면,

삭제가 완료되고, 게시판 목록으로 redirect된다.

 


 

해당 글은 코딩레시피님의 스프링 부트 쉽게 해보기 : 게시판 프로젝트 영상을 보고 공부한 글입니다.

https://youtu.be/mJytzx8qIRI?si=2ETn18NHfApV5zSh