logoStephen's 기술블로그

포스트 검색

제목, 태그로 포스트를 검색해보세요

#블로그만들기_15 #게시글 수정하기

#블로그만들기_15 #게시글 수정하기
SpringWebApp
성훈 김
2024년 2월 11일
목차

1️⃣ View 먼저 확인

💡
🔹 /board/1/update → 어떤 게시글을 업데이트 하냐라는 로직이 필요하기 때문에 id가 필요하다. 🔹 게시글 수정 버튼을 눌렸을 때, 내용과 제목이 있어야 하므로 DB조회가 필요하다.
notion image
 
 

2️⃣ updateForm.mustache 코드 구현

💡
saveForm.mustache를 복붙해서 updateForm.mustache을 만들어 준다. 🔹 action 링크 확인 → 1은 변수로 바꿔야 된다. 🔹 method → post 🔹 enctype → xform 데이터 🔹 키값 확인 → title, content 🔹 button 타입확인 → submit은 폼태그 안에 있으면 양식을 제출한다.
notion image
 
 
💡
updateForm.mustache 코드 🔹 글 수정화면으로 사용하기 때문에, 메시지도 아래처럼 수정해준다.
HTML
{{> layout/header}}

<div class="container p-5">
    <div class="card">
        <div class="card-header"><b>수정하기 화면입니다</b></div>
        <div class="card-body">
            <form action="/board/{id}/update" method="post" enctype="application/x-www-form-urlencoded">
                <div class="mb-3">
                    <input type="text" class="form-control" placeholder="Enter title" name="title" value="제목1">
                </div>
                <div class="mb-3">
                    <textarea class="form-control" rows="5" name="content">내용 1</textarea>
                </div>
                <button type="submit" class="btn btn-primary form-control">수정완료</button>
            </form>
        </div>
    </div>
</div>

{{> layout/footer}}
 
 

3️⃣ updateForm로직

💡
🔹 부가로직을 구현하느라 핵심로직을 잊어버리지 말자!! 🔹 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를 연결한다.
notion image
 
 
💡
🔹 아래 DetailDTO에서 Mustache 문법으로 {{ 변수.필드값 }} 으로 해당 필드값을 가져와 할당할 수 있다. 🔹 {{board.id}} / {{board.title}} / {{board.content}}
notion image
💡
View에 연동하기
notion image
 
 

🔹 수정버튼에 updateForm으로 이동기능 만들기

💡
🔹 상세보기 화면에서 수정버튼을 누르면 updateForm으로 이동하는 버튼 만들기 🔹 Get요청만 하는 거라면 a태그를 사용해서 링크를 붙여도 된다. 🔹 {{id}} 는 컨트롤러에서 @PathVariable로 값을 할당받게 될 것이다.
notion image
 
 
💡
테스트 🔹 수정을 클릭하면 URL이 게시글 번호를 받아서 updateForm페이지로 가야된다.
notion image
 
 
💡
성공! 🔹 수정화면의 내부역시 DB로 조회된 내용을 화면에 출력해야 된다. 🔹 /board/3/update으로 URL 잘 적용이 되었다. 🔹 3번 게시물이므로 제목3, 내용3이 표현되었고 잘 적용이 된 것 같다!
notion image
 
 
 

4️⃣ 게시물 수정기능 구현하기(액션)

🔹 DTO만들기

💡
게시물 제목과 내용을 수정하여 버튼을 클릭하면 전달되는 정보는 titlecontent이다. 이를 위한 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이 들어가있기 때문에, 브라우저에서 바로 테스트 해볼 수 있다.
notion image
⚠️
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 어노테이션이 붙어 있는지 다시 한 번 확인하자.
동기화란?? 🔹 다른 작업이 못 끼어들게 하여, 프로세스를 독점 → 작업의 무결성
 
 
 
💡
수정 테스트 확인완료!
notion image
 
 
 

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 등의 형식으로 전송된 데이터를 해당 메서드의 파라미터로 매핑할 때 사용된다. 🔹 여기서는 사용안할 것이기 때문에 알아만 놓자.\
notion image
 
💡
아래 코드에서 @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는 해당 원문을 자바 객체로 변환시켜서 저장한다.
notion image