스프링 부트 조회 - seupeuling buteu johoe

1. 게시글 목록

1-1 HTML

src/main/resources/static/css/board.css

table {
    border-collapse: collapse;
}

table, th, td {
    border: 1px solid black;
}

게시글 목록의 테이블을 꾸며주는 css

다음은 게시글 리스트 페이지이다

src/main/resources/templates/board/list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    <link rel="stylesheet" th:href="@{/css/board.css}">
</head>
<body>
    <!-- HEADER -->
    <div th:insert="common/header.html" id="header"></div>

    <a th:href="@{/post}">글쓰기</a>

    <table>
        <thead>
        <tr>
            <th class="one wide">번호</th>
            <th class="ten wide">글제목</th>
            <th class="two wide">작성자</th>
            <th class="three wide">작성일</th>
        </tr>
        </thead>

        <tbody>
        <!-- CONTENTS !-->
        <tr th:each="board : ${boardList}">
            <td>
                <span th:text="${board.id}"></span>
            </td>
            <td>
                <a th:href="@{'/post/' + ${board.id}}">
                    <span th:text="${board.title}"></span>
                </a>
            </td>
            <td>
                <span th:text="${board.writer}"></span>
            </td>
            <td>
                <span th:text="${#temporals.format(board.createdDate, 'yyyy-MM-dd HH:mm')}"></span>
            </td>
        </tr>
        </tbody>
    </table>

    <!-- FOOTER -->
    <div th:insert="common/footer.html" id="footer"></div>
</body>
</html>

<link rel="stylesheet" th:href="@{/css/board.css}">

 - 앞에서 작성한 board.css를 불러오는 코드

 - css, js, img 같은 정적 자원들을 src/main/resourses/static 경로에 저장하면 스프링 부트가 인식하게 된다

<tr th:each="board : ${boardList}">

 - thymeleaf에서 반복문을 사용하는 부분이다

 - 컨트롤러가 넘겨주는 변수는 ${}으로 받을 수 있다

    - 즉, boardList는 컨트롤러가 넘겨주는 변수이며, 원소는 board 변수로 사용하여 각 속성을 사용할 수 있다

<span th:text="${board.id}"></span>

 - 변수 값을 escape 처리하여, 태그의 텍스트로 사용

<a th:href="@{'/post/' + ${board.id}}">

 - 글 제목을 누르면 상세페이지로 이동하기 위해 PathVariable을 사용

<span th:text="${#temporals.format(board.createdDate, 'yyyy-MM-dd HH:mm')}"></span>

 - #temporals.format()메서드를 사용하여 날짜를 포맷팅 하는 방법

 - createdDate는 LacalDateTime타입이기 때문에 java.util.Date를 사용하는 #dates.format()을 사용하지 않는다

1-2 Controller

src/main/java/com/project/springbootproject/controller/BoardController.java

import com.victolee.board.dto.BoardDto;
import com.victolee.board.service.BoardService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.List;

@Controller
@AllArgsConstructor
public class BoardController {
    private BoardService boardService;

    /* 게시글 목록 */
    @GetMapping("/")
    public String list(Model model) {
        List<BoardDto> boardList = boardService.getBoardlist();

        model.addAttribute("boardList", boardList);
        return "board/list.html";
    }



    ...

}

public String list(Model model) { }

 - Model 객체를 통해 View에 데이터를 전달한다

1-3 Service

src/main/java/com/project/springbootproject/service/BoardService.java

import com.victolee.board.domain.entity.BoardEntity;
import com.victolee.board.domain.repository.BoardRepository;
import com.victolee.board.dto.BoardDto;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;

@AllArgsConstructor
@Service
public class BoardService {
    private BoardRepository boardRepository;

    @Transactional
    public List<BoardDto> getBoardlist() {
        List<BoardEntity> boardEntities = boardRepository.findAll();
        List<BoardDto> boardDtoList = new ArrayList<>();

        for ( BoardEntity boardEntity : boardEntities) {
            BoardDto boardDTO = BoardDto.builder()
                    .id(boardEntity.getId())
                    .title(boardEntity.getTitle())
                    .content(boardEntity.getContent())
                    .writer(boardEntity.getWriter())
                    .createdDate(boardEntity.getCreatedDate())
                    .build();

            boardDtoList.add(boardDTO);
        }

        return boardDtoList;
    }



    ...

}

Controller와 Service간에 데이터 전달은 dto 객체로 하기 위해, Repository에서 가져온 Entity를 반복문을 통해 dto로 변환하는 작업이 있다

1-4 테스트

여기까지 코드를 작성했다면 프로젝트를 실행해서 목록이 잘 출력 되는지 확인해보자

스프링 부트 조회 - seupeuling buteu johoe

2. 게시글 조회, 수정, 삭제

다음으로 게시글 제목을 클릭하면 이동되는 상세페이지와 수정, 삭제 모두 구현해보자

2-1 게시글 상세조회 페이지 -js

src/main/resources/static/js/board.js

console.log(boardDto);

boardDto를 콘솔로 찍어보는 스크립트이다

컨트롤러에서 넘겨준 Java 변수를 JS에서 어떻게 사용할 수 있는지 알아보기 위한 예제임

2-2 게시글 상세조회 페이지 + 수정, 삭제 버튼

src/main/resources/templates/board/detail.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h2 th:text="${boardDto.title}"></h2>
<p th:inline="text">작성일 : [[${#temporals.format(boardDto.createdDate, 'yyyy-MM-dd HH:mm')}]]</p>

<p th:text="${boardDto.content}"></p>

<!-- 수정/삭제 -->
<div>
    <a th:href="@{'/post/edit/' + ${boardDto.id}}">
        <button>수정</button>
    </a>

    <form id="delete-form" th:action="@{'/post/' + ${boardDto.id}}" method="post">
        <input type="hidden" name="_method" value="delete"/>
        <button id="delete-btn">삭제</button>
    </form>
</div>

<!-- 변수 셋팅 -->
<script th:inline="javascript">
    /*<![CDATA[*/
    var boardDto = /*[[${boardDto}]]*/ "";
    /*]]>*/
</script>

<!-- script -->
<script th:inline="javascript" th:src="@{/js/board.js}"></script>
</body>
</html>

<p th:inline="text">작성일 : [[${#temporals.format(boardDto.createdDate, 'yyyy-MM-dd HH:mm')}]]</p>

 - th:text를 사용하면, 태그 사이에 작성한 내용은 사라지고 th:text 값으로 덮어 써진다

 - 이를 해결하기 위해 th:inline를 사용하며, 변수는 [[ ${ } ]]으로 표기<input type="hidden" name="_method" value="delete"/>

 - RESTful API 작성을 위해 hiddenHttpMethodFilter를 이용

 - 그러면 form 태그의 method는 post이지만, 실제로는 컨트롤러에서 delete로 매핑이 된다

/*<![CDATA[*/ ~~~ /*]]>*/

 - JS에서 Java 변수를 사용하기 위한 방식

 - 위에서 boardDto를 콘솔로 출력하는 스크립트를 작성하였으므로 게시글 상세 페이지에 접근 시, 개발자 도구 콘솔 창에서 확인할 수 있다

2-3 게시글 수정 페이지

src/main/resources/templates/board/update.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form th:action="@{'/post/edit/' + ${boardDto.id}}" method="post">
    <input type="hidden" name="_method" value="put"/>
    <input type="hidden" name="id" th:value="${boardDto.id}"/>

    제목 : <input type="text" name="title" th:value="${boardDto.title}"> <br>
    작성자 : <input type="text" name="writer" th:value="${boardDto.writer}"> <br>
    <textarea name="content" th:text="${boardDto.content}"></textarea><br>

    <input type="submit" value="수정">
</form>
</body>
</html>

<input type="hidden" name="_method" value="put"/>

 - 마찬가지로 Restful API 작성을 위한 것으로, 컨트롤러에서 put 메서드로 매핑<input type="hidden" name="id" th:value="${boardDto.id}"/>

 - hidden 타입을 게시글 id 값을 넘겨준 이유는 JPA(BoardRepository.save())에서 insert와 update를 같은 메서드로

   사용하기 때문이다

 - 즉, 같은 메서드를 호출하는데 id 값이 없다면 insert가 되는 것이고, id 값이 이미 존재한다면 update가 되도록

   동작된다. 따라서 Service에서 update를 위한 메서드는 없고, insert와 같은 메서드를 사용한다

2-4 Controller

src/main/java/com/project/springbootproject/controller/BoardController.java

import com.victolee.board.dto.BoardDto;
import com.victolee.board.service.BoardService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Controller
@AllArgsConstructor
public class BoardController {
    private BoardService boardService;

    ...


    @GetMapping("/post/{no}")
    public String detail(@PathVariable("no") Long no, Model model) {
        BoardDto boardDTO = boardService.getPost(no);

        model.addAttribute("boardDto", boardDTO);
        return "board/detail.html";
    }

    @GetMapping("/post/edit/{no}")
    public String edit(@PathVariable("no") Long no, Model model) {
        BoardDto boardDTO = boardService.getPost(no);

        model.addAttribute("boardDto", boardDTO);
        return "board/update.html";
    }

    @PutMapping("/post/edit/{no}")
    public String update(BoardDto boardDTO) {
        boardService.savePost(boardDTO);

        return "redirect:/";
    }

    @DeleteMapping("/post/{no}")
    public String delete(@PathVariable("no") Long no) {
        boardService.deletePost(no);

        return "redirect:/";
    }



    ...
}

위에서부터 차례대로 게시글 상세조회 페이지, 게시글 수정 페이지, 게시글 수정, 게시글 삭제

@GetMapping("/post/{no}")

@PathVariable("no") Long no

 - 유동적으로 변하는 PathVariable을 처리하는 방법

 - URL 매핑하는 부분에서 {변수} 처리를 해주면, 메서드 파라미터로 @PathVariable("변수") 이렇게 받을 수 있다

update()

 - 게시글 추가에서 사용하는 boardService.savePost() 메서드를 같이 사용하고 있다

2-5 Service

src/main/java/com/project/springbootproject/service/BoardService.java

import com.victolee.board.domain.entity.BoardEntity;
import com.victolee.board.domain.repository.BoardRepository;
import com.victolee.board.dto.BoardDto;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@AllArgsConstructor
@Service
public class BoardService {
    private BoardRepository boardRepository;

    ...

    @Transactional
    public BoardDto getPost(Long id) {
        Optional<BoardEntity> boardEntityWrapper = boardRepository.findById(id);
        BoardEntity boardEntity = boardEntityWrapper.get();

        BoardDto boardDTO = BoardDto.builder()
                .id(boardEntity.getId())
                .title(boardEntity.getTitle())
                .content(boardEntity.getContent())
                .writer(boardEntity.getWriter())
                .createdDate(boardEntity.getCreatedDate())
                .build();

        return boardDTO;
    }

    @Transactional
    public Long savePost(BoardDto boardDto) {
        return boardRepository.save(boardDto.toEntity()).getId();
    }

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

findById()

 - PK 값을 where 조건으로 하여, 데이터를 가져오기 위한 메서드이며, JpaRepository 인터페이스에서 정의되어 있다

 - 반환 값은 Optional 타입인데, 엔티티를 쏙 빼오려면 boardEntityWrapper.get(); 이렇게 get() 메서드를 사용해서

   가져오면 된다

deleteById()

 - PK값을 where 조건으로 하여, 데이터를 삭제하기 위한 메서드이며, JpaRepository 인터페이스에서 정의되어 있다

3. 최종 테스트

조회, 수정, 삭제가 잘 작동하는지 확인해보자

스프링 부트 조회 - seupeuling buteu johoe

게시글 작성

스프링 부트 조회 - seupeuling buteu johoe

상세조회

스프링 부트 조회 - seupeuling buteu johoe

게시글 수정

스프링 부트 조회 - seupeuling buteu johoe

제대로 수정된 걸 확인할 수 있다

스프링 부트 조회 - seupeuling buteu johoe

마지막으로 삭제까지 깔꼼쓰


출처 https://victorydntmd.tistory.com/327