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 테스트
여기까지 코드를 작성했다면 프로젝트를 실행해서 목록이 잘 출력 되는지 확인해보자
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. 최종 테스트
조회, 수정, 삭제가 잘 작동하는지 확인해보자
게시글 작성
상세조회
게시글 수정
제대로 수정된 걸 확인할 수 있다
마지막으로 삭제까지 깔꼼쓰
'Spring Boot Project' 카테고리의 다른 글
Spring Boot 게시판 만들기 [2] 게시글 추가하기 (3) | 2020.08.21 |
---|---|
Spring Boot 게시판 만들기 [1] 개발환경 구축하기 (1) | 2020.08.20 |