🔹 /board/1/update → 어떤 게시글을 업데이트 하냐라는 로직이 필요하기 때문에 id가 필요하다.
🔹 게시글 수정 버튼을 눌렸을 때, 내용과 제목이 있어야 하므로 DB조회가 필요하다.
2️⃣ updateForm.mustache 코드 구현
💡
saveForm.mustache를 복붙해서 updateForm.mustache을 만들어 준다.
🔹 action 링크 확인 → 1은 변수로 바꿔야 된다.
🔹 method → post
🔹 enctype → xform 데이터
🔹 키값 확인 → title, content
🔹 button 타입확인 → submit은 폼태그 안에 있으면 양식을 제출한다.
💡
updateForm.mustache 코드
🔹 글 수정화면으로 사용하기 때문에, 메시지도 아래처럼 수정해준다.
🔹 부가로직을 구현하느라 핵심로직을 잊어버리지 말자!!
🔹 updateForm의 핵심로직은 board를 조회해서 request에 담아서 view에 전달하는 것이 핵심이다!
🔹 반복 숙달이 되면 부가로직은 주석으로만 남겨 놓고, 핵심로직 구현후에 복붙하자.
🔹 @PathVariable 적극 사용!!
Java
@RequiredArgsConstructor
@Controller
public class BoardController {
private final HttpSession session;
private final BoardRepository boardRepository;
// 게시글 수정 페이지 정보를 조회해서 뿌리는 책임을 가진다.
// @PathVariable 사용해서 동적으로 할당한다.
@GetMapping("/board/{id}/updateForm")
public String updateForm (@PathVariable int id, HttpServletRequest request) {
// 인증 체크
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) {
return "redirect:/loginForm";
}
// 모델 위임 (id로 board를 조회)
// 권한 체크
BoardResponse.DetailDTO detailDTO = boardRepository.findById(id);
if (sessionUser.getId() != detailDTO.getUserId()){
request.setAttribute("msg", "권한이 없습니다.");
request.setAttribute("status", 403);
}
// 가방에 담기
// 가방에 담아서 View에 전달하는 것이 핵심로직이다
request.setAttribute("board", detailDTO);
return "board/updateForm";
}
📝
Additional Notes
🔹 DB를 조회하기 위해서 번호가 필요하다.
🔹 Join을 사용하게 되면 DB부하가 커진다. 조심해서 사용!!
➖ 서버부하가 커지는 쿼리 → join, group by, sub쿼리
🔹 PK로 조회하면 인덱스로 조회하기 때문에 엄청 빠르다.
🔹 평점이라는 기능 역시 사람들의 평가가 필요하므로 스칼라가 아닌 테이블로 만들어야된다.
🔹 DetailDTO를 board에 할당하기
💡
🔹 BoardResponse.DetailDTO 는 변수 detailDTO라는 변수으로 지정이 되어있다.
🔹 request.setAttribute로 키 값을 board로 지정하고 필드값으로 detailDTO를 연결한다.
💡
🔹 아래 DetailDTO에서 Mustache 문법으로 {{ 변수.필드값 }} 으로 해당 필드값을 가져와 할당할 수 있다.
🔹 {{board.id}} / {{board.title}} / {{board.content}}
💡
View에 연동하기
🔹 수정버튼에 updateForm으로 이동기능 만들기
💡
🔹 상세보기 화면에서 수정버튼을 누르면 updateForm으로 이동하는 버튼 만들기
🔹 Get요청만 하는 거라면 a태그를 사용해서 링크를 붙여도 된다.
🔹 {{id}} 는 컨트롤러에서 @PathVariable로 값을 할당받게 될 것이다.
💡
테스트
🔹 수정을 클릭하면 URL이 게시글 번호를 받아서 updateForm페이지로 가야된다.
💡
성공!
🔹 수정화면의 내부역시 DB로 조회된 내용을 화면에 출력해야 된다.
🔹 /board/3/update으로 URL 잘 적용이 되었다.
🔹 3번 게시물이므로 제목3, 내용3이 표현되었고 잘 적용이 된 것 같다!
4️⃣ 게시물 수정기능 구현하기(액션)
🔹 DTO만들기
💡
게시물 제목과 내용을 수정하여 버튼을 클릭하면 전달되는 정보는 title과 content이다. 이를 위한 DTO를 따로 만들어 최대한 SRP원칙을 지키도록 한다.
Java
package shop.mtcoding.blog.board;
import lombok.Data;
public class BoardResponse {
// 게시물 제목과 내용을 적어서 버튼을 클릭하면
// 전달되는 정보는 title과 content이다.
@Data
public static class UpdateDTO {
private String title;
private String content;
}
🔹 컨트롤러>>update() 메소드 핵심로직 먼저 만들기
💡
🔹 인증체크랑 권한 체크는 이미 모듈화가 되어있으므로 핵심로직부터 구현한다.
🔹 redirect로 반환 → PRG 패턴 (중복 Post요청을 방지하기 위함이다)
🔹 PRG패턴 이란 → Post요청 >> Redirect >> Get요청
Java
@RequiredArgsConstructor
@Controller
public class BoardController {
private final BoardRepository boardRepository;
private final HttpSession session;
@PostMapping("board/{id}/update")
public String update (@PathVariable int id, BoardResponse.UpdateDTO requestDTO){
//1. 인증 체크
//2. 권한 체크
//3. 핵심 로직
// 쿼리 >> update board_tb set title=? , content=? where id=?
boardRepository.update(id, requestDTO);
return "redirect:/board/" + id;
}
}
5️⃣ DB연결하기
💡
🔹 DB변경이 일어나므로 @Transactional 어노테이션을 붙인다.
🔹 위치 BoardRepository>> update() 메소드
🔹 쿼리문 = “update board_tb set title=? , content=? where id=? ”
Java
@Transactional // DB변경이기 때문에 최소작업단위로 묶어야된다.
public void update(int id, BoardRequest.UpdateDTO requestDTO) {
Query query = em.createNativeQuery("update board_tb set title=? , content=? where id=? ");
query.setParameter(1, requestDTO.getTitle());
query.setParameter(2, requestDTO.getContent());
query.setParameter(3, id);
query.executeUpdate();
}
🔹 테스트 하기
💡
이 수정 완료 버튼에는 폼태그(<form></form>)로 post요청과 submit이 들어가있기 때문에, 브라우저에서 바로 테스트 해볼 수 있다.
⚠️
TransactionRequiredException에러가 생길경우!해결방법 펼쳐보기
💡
원문 메시지
Java
2024-02-11T21:31:05.611+09:00 ERROR 35720 --- [io-8080-exec-10]
o.a.c.c.C.[.[.[/].[dispatcherServlet] :
Servlet.service() for servlet [dispatcherServlet] in context
with path [] threw exception [Request processing failed:
org.springframework.dao.InvalidDataAccessApiUsageException:
Executing an update/delete query] with root cause
jakarta.persistence.TransactionRequiredException:
Executing an update/delete query
💡
🔹 이 메시지는 DB변경이 일어나면 @Transactional 어노테이션을 붙여서, 쿼리 작업을 강제적으로 동기화 시키기를 요구한다.
🔹 update쿼리가 들어간 메소드 위에 @Transactional 어노테이션이 붙어 있는지 다시 한 번 확인하자.
⭐
동기화란??
🔹 다른 작업이 못 끼어들게 하여, 프로세스를 독점 → 작업의 무결성
💡
수정 테스트 확인완료!
6️⃣ update()메소드 부가로직 구현
💡
부가로직은 다음과 같다.
🔹 로그인 여부 검사 → 인증 테스트, 401
🔹 게시물 주인 판단 검사 → 권한 테스트, 403
Java
@RequiredArgsConstructor
@Controller
public class BoardController {
private final BoardRepository boardRepository;
private final HttpSession session;
@PostMapping("board/{id}/update")
public String update (@PathVariable int id, BoardResponse.UpdateDTO requestDTO, HttpServletRequest request){
//1. 인증 체크
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null){
return "redirect:/loginForm";
}
//2. 권한 체크
BoardResponse.DetailDTO board = boardRepository.findById(id);
if (board.getUserId()!=sessionUser.getId()){
request.setAttribute("msg", "권한이 없습니다.");
request.setAttribute("status", 403);
return "error/40x";
}
//3. 핵심 로직
// 쿼리 >> update board_tb set title=? , content=? where id=?
boardRepository.update(id, requestDTO);
return "redirect:/board/" + id;
}
⭐
Additional Notes - @RequestBody어노테이션
💡
🔹@RequestBody는 Spring Framework에서 사용하는 어노테이션으로, 클라이언트로부터 오는 HTTP 요청의 본문(body)을 Java 객체로 변환해주는 역할을 한다.
🔹 이 어노테이션은 주로 POST나 PUT 요청에서 사용되며, JSON, XML 등의 형식으로 전송된 데이터를 해당 메서드의 파라미터로 매핑할 때 사용된다.
🔹 여기서는 사용안할 것이기 때문에 알아만 놓자.\
💡
아래 코드에서 @RequestBody 어노테이션의 위치를 살펴보자!
Java
@PostMapping("/board/{id}/update")
public void update (@PathVariable int id, @RequestBody BoardRequest.UpdateDTO requestDTO) {
System.out.println(requestDTO);
}
💡
Postman으로 raw섹션에 JSON타입의 자료를 입력하여 Post요청을 하면 @RequestBody는 해당 원문을 자바 객체로 변환시켜서 저장한다.