게시글 작성-목록출력 -조회 까지 왔고 이제 수정 단계입니다.
크게 순서는 게시글 상세 화면에서 수정 버튼 클릭 서버에서 해당 게시글의 정보를 가지고 수정화면 출력
수정은 2단계로 거처서 진행이 됩니다.
1. 수정을 하고싶다고 하면 수정화면 출력
2.제목,내용 수정 내용을 입력 받아서 서버로 요청
3.수정 처리
게시글 상세페이지 글수정 버튼 생성 ->컨트롤러 작성->글수정 페이지 작성 -> 컨트롤러 마저 작성-> 서비스에서 db를 받아올 메소드 작성->Entity에서 DTO로 변환 ->다시 서비스 마저 작성 확인
처음 시작은 게시판 상세 페이지 detail.html에서 부터 시작합니다
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.6.3.min.js" integrity="sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU=" 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> <!-- 추가 밑에 updateReq = () => { 내용도 추가.-->
<button onclick="deleteReq()">삭제</button>
</body>
<script th:inline="javascript">
const listReq = () => {
console.log("목록 요청");
const page = [[${page}]];
location.href = "/board/paging?page="+page;
}
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
package cho.boardplus.controller;
import cho.boardplus.dto.BoardDTO;
import cho.boardplus.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RequiredArgsConstructor
@RequestMapping("/board")
@Controller
public class BoardController {
private final BoardService boardService;
//메인페이지
@GetMapping("/index")
public String index() {
return "index";
}
//글 작성 페이지
@GetMapping("/save")
public String writeForm() {
return "save";
}
// 글작성 컨트롤러
@PostMapping("save")
public String save(@ModelAttribute BoardDTO boardDTO) {
System.out.println("boardDTO = " + boardDTO);
boardService.save(boardDTO);
return "index"; //index 로 변경
}
// 게시글 목록
@GetMapping("/")
public String findAll(Model model) {
// DB에서 전체 게시글 데이터를 가져와서 list.html에 보여준다.
List<BoardDTO> boardDTOList = boardService.findAll();
model.addAttribute("boardList", boardDTOList);
return "list";
}
//게시글 조회
@GetMapping("/{id}")
public String findById(@PathVariable Long id, Model model){
boardService.updateHits(id);
BoardDTO boardDTO =boardService.findById(id);
model.addAttribute("board",boardDTO);
return "detail";
}
//게시글 수정
@GetMapping("/update/{id}") //추가
public String updateForm(@PathVariable Long id,Model model) {
BoardDTO boardDTO = boardService.findById(id);
model.addAttribute("boardUpdate",boardDTO);
return "update";
}
}
게시글 수정도 get방식 입니다.
- 게시글 조회처럼 똑같이 PathVariable을 사용하고 데이터를 담아 가야하기 때문에 Model 이 필요합니다.
- 해당 게시글의 정보만 가져오면 되는 거기 때문에 BoardDTO boardDTO = boardService.finById(id ) 그후 모델에 담습니다 그것이 model.addAttribute("boardUpdate,boardDTO) 입니다.
- 그후 리턴 값으로 "update" html 단으로 넘겨주면 됩니다.
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>
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 = () => {
document.updateForm.submit();
}
</script>
</body>
</html>
- 사용자 입력을 받아야 하기 때문에 form 태그를 써야합니다.
- 입력을 받아야 하기때문에 input 태그 필요
- 모든 정보를 다 가지고 있어야 합니다. 굳이 수정하지 않는 정보라도 hidden 으로 같이 가지고 있어야 합니다.
보여주고 싶지 않은 정보들은 hidden으로 숨기시면 됩니다. 게시글 id 도 보시면 hidden으로 숨겻습니다. - 작성자 같은 경우 보여 주지만 수정을 하지못하게 할려고 했습니다 그럴때 readonly를 사용하면 됩니다.
- 그리고 자바 스크립으로 글수정 버튼을 눌렀을때 값을 받아오도록 함수를 정의 하였습니다. const boardUpdate = () => 부분입니다.
아직 수정본을 받을 메소드 들을 작성을 안해서 db 정보를 받는 메소드를 작성로하로 가야합니다.
다시 컨트롤러로 돌아가야합니다.
BoardController
package cho.boardplus.controller;
import cho.boardplus.dto.BoardDTO;
import cho.boardplus.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RequiredArgsConstructor
@RequestMapping("/board")
@Controller
public class BoardController {
private final BoardService boardService;
//메인페이지
@GetMapping("/index")
public String index() {
return "index";
}
//글 작성 페이지
@GetMapping("/save")
public String writeForm() {
return "save";
}
// 글작성 컨트롤러
@PostMapping("save")
public String save(@ModelAttribute BoardDTO boardDTO) {
System.out.println("boardDTO = " + boardDTO);
boardService.save(boardDTO);
return "index"; //index 로 변경
}
// 게시글 목록
@GetMapping("/")
public String findAll(Model model) {
// DB에서 전체 게시글 데이터를 가져와서 list.html에 보여준다.
List<BoardDTO> boardDTOList = boardService.findAll();
model.addAttribute("boardList", boardDTOList);
return "list";
}
//게시글 조회
@GetMapping("/{id}")
public String findById(@PathVariable Long id, Model model){
boardService.updateHits(id);
BoardDTO boardDTO =boardService.findById(id);
model.addAttribute("board",boardDTO);
return "detail";
}
//게시글 수정
@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(); // 이것도 가능
}
}
- 수정이 완료되고나서 목록을 띄어줘도 괜찮고 내가 수정했던 게시글 을 띄어줘도 괜찮습니다. 이건 자기가 선택을 하는겁니다.
- 바로 위 단계랑 상황이 비슷해서 모델로 값을 받아 내고 리턴값으로 돌려주면 됩니다.
boardService.update(boardDTO) 이부분중에 update 부분이 오류가 나 있을겁니다. 이제 서비스 단에 가서 update클래스를 작성하로 갑시다.
BoardService
package cho.boardplus.service;
import cho.boardplus.repository.BoardRepository;
import cho.boardplus.dto.BoardDTO;
import cho.boardplus.entity.BoardEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
//DTO -> Entity (Entity Class)
//Entity -> DTO (DTO Class)
@Service
@RequiredArgsConstructor
public class BoardService {
private final BoardRepository boardRepository;
//작성
public void save(BoardDTO boardDTO) {
BoardEntity boardEntity = BoardEntity.toSaveEntity(boardDTO);
boardRepository.save(boardEntity);
}
public List<BoardDTO> findAll() {//파인드 올은 대부분 엔티티 한태서 온다.
List<BoardEntity> boardEntityList = boardRepository.findAll();
List<BoardDTO> boardDTOList = new ArrayList<>();
for (BoardEntity boardEntity : boardEntityList) {
boardDTOList.add(BoardDTO.toBoardDTO(boardEntity));
}
return boardDTOList;
}
// 게시글 조회수
@Transactional
public void updateHits(Long id) {
boardRepository.updateHits(id);
}
//게시글 상세조회
@Transactional
public BoardDTO findById(Long id) {
Optional<BoardEntity> optionalBoardEntity = boardRepository.findById(id);
if (optionalBoardEntity.isPresent()) {
BoardEntity boardEntity = optionalBoardEntity.get();
BoardDTO boardDTO = BoardDTO.toBoardDTO(boardEntity);
return boardDTO;
} else {
return null;
}
}
public BoardDTO update(BoardDTO boardDTO) { //추가
BoardEntity boardEntity = BoardEntity.toUpdateEntity(boardDTO);
boardRepository.save();
}
}
- JPA 에서 업데이트를 따로 해주는 메소드는 해당글 작성기준 없습니다. 스프링 data JPA는 save 메소드를 가지고 업데이트도 하고 insert도 하고 2가지가 가능합니다. 그렇다면 어떻게 insert인지 업데이트인지 구분을 하냐면 기준은 id 값이 있냐 없냐 그차이 입니다. 아이디 값이 존재한 상태로 Entity 객체가 넘어오게 되면은 그 id 값이 db에 존재 하는것이라면 JPA는 업데이트를 하라는 명령으로 받아 드립니다. 그래서 업데이트 작업을 할때도 save() 메소드를 호출 해야합니다.
- 그후 엔티티를 변환하는 작업이 필요합니다. 그때 사용한것이 BoardEntity boardEntity = boardEntity.toUpdateEntity(boardDTO); 입니다.
toUpdateEntity 가 오류를 발생 시킬겁니다. Entity에서 변환작업을 안해줫기 때문에 BoardEntity 로 가서 메소드를 작성하로 갑시다.
BoardEntity
package cho.boardplus.entity;
import cho.boardplus.dto.BoardDTO;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Getter
@Setter
@Table(name= "board")
public class BoardEntity extends BaseEntity {
@Id //pk 컬럼 지정. 필수
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id; //id 값
@Column(length = 20, nullable = false)//크기 20, not null
private String boardWriter;// 글 작성자
@Column
private String boardTitle; //제목
@Column(length = 500)
private String boardContents; //내용
@Column
private int boardHits;//조회수
//entity 를 DTO 로 변환하는 작업
public static BoardEntity toSaveEntity(BoardDTO boardDTO) {
BoardEntity boardEntity = new BoardEntity();
boardEntity.setBoardWriter(boardDTO.getBoardWriter());
boardEntity.setBoardTitle(boardDTO.getBoardTitle());
boardEntity.setBoardContents(boardDTO.getBoardContents());
boardEntity.setBoardHits(0);
return boardEntity;
}
public static BoardEntity toUpdateEntity(BoardDTO boardDTO) { //추가
BoardEntity boardEntity = new BoardEntity();
boardEntity.setId(boardDTO.getId()); // 위쪽과 다르게 추가된곳
boardEntity.setBoardWriter(boardDTO.getBoardWriter());
boardEntity.setBoardTitle(boardDTO.getBoardTitle());
boardEntity.setBoardContents(boardDTO.getBoardContents());
boardEntity.setBoardHits(boardDTO.getBoardHits()); //조회수 부분도 약간 다르게 작성됨
return boardEntity;
}
}
- tosaveEntity랑 다른점은 id 값이 추가 돼었고 조회수 부분도 약간의 변경이 있습니다.
id 값이 있어야 업데이트 쿼리가 전달이 될수 있기때문에 id 값이 핵심입니다.
다시 service 단으로 가서 마저 작성을 해줘야 합니다.
BoardService
package cho.boardplus.service;
import cho.boardplus.repository.BoardRepository;
import cho.boardplus.dto.BoardDTO;
import cho.boardplus.entity.BoardEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
//DTO -> Entity (Entity Class)
//Entity -> DTO (DTO Class)
@Service
@RequiredArgsConstructor
public class BoardService {
private final BoardRepository boardRepository;
//작성
public void save(BoardDTO boardDTO) {
BoardEntity boardEntity = BoardEntity.toSaveEntity(boardDTO);
boardRepository.save(boardEntity);
}
public List<BoardDTO> findAll() {//파인드 올은 대부분 엔티티 한태서 온다.
List<BoardEntity> boardEntityList = boardRepository.findAll();
List<BoardDTO> boardDTOList = new ArrayList<>();
for (BoardEntity boardEntity : boardEntityList) {
boardDTOList.add(BoardDTO.toBoardDTO(boardEntity));
}
return boardDTOList;
}
// 게시글 조회수
@Transactional
public void updateHits(Long id) {
boardRepository.updateHits(id);
}
//게시글 상세조회
@Transactional
public BoardDTO findById(Long id) {
Optional<BoardEntity> optionalBoardEntity = boardRepository.findById(id);
if (optionalBoardEntity.isPresent()) {
BoardEntity boardEntity = optionalBoardEntity.get();
BoardDTO boardDTO = BoardDTO.toBoardDTO(boardEntity);
return boardDTO;
} else {
return null;
}
}
public BoardDTO update(BoardDTO boardDTO) {
BoardEntity boardEntity = BoardEntity.toUpdateEntity(boardDTO);
boardRepository.save(boardEntity); //save() 괄호 안에 Entity 추가
return findById(boardDTO.getId()); //추가
}
}
- save() 안에 boardEntity 를 넘겨주고 그러면 이제 업데이트 쿼리가 수행이 될겁니다. 그다음으로 게시글의 상세 조회값을 념겨 줘야 하니 return 값으로 finById(boardDTO.getId()) 작성합니다 레포지토리에서 findById 를 할시 옵션을 오고 그걸다시 제 가공해야 하는 번거로움이 있기 때문에 service 단 위에 전에 작성 했던 findById 메소드가 옵션을 까서 가져오는 작업을 구현한것이 이미 윗단에 미리 작업을 했기때문에 전에 작성했던 메소드 findById 를 다시 호출해서 controller 단으로 넘겨주는 작업을 했습니다.
db를 확인합시다.
수고하셧습니다.