사이먼's 코딩노트

[SpringBoot] Prefix 적용 / Form 만들기 본문

Java/SpringBoot

[SpringBoot] Prefix 적용 / Form 만들기

simonpark817 2024. 5. 9. 10:17

[Prefix 적용]

  • 현재까지 구현된 QuestionController.java 클래스를 살펴보면 2개의 URL 매핑이 되어있는 것을 확인할 수 있다.
  • @GetMapping("/question/list")와 @GetMapping("/question/detail/{id}") 두 개다.
  • Prefix란, 접두사 또는 시작부분을 가르키는 의미로 여기선 URL의 프리픽스가 모두 /question 으로 시작한다는 것을 알 수 있다.
  • 다시 말해, QuestionController.java 클래스에 속하는 URL은 모두 프리픽스가 /question 으로 시작하도록 설정할 수 있다.
  • 아래는 수정된 QuestionController.java 클래스의 코드이다.
package com.sbs.sbb.Question;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RequestMapping("/question")      
@Controller
@RequiredArgsConstructor              
public class QuestionController {
    private final QuestionService questionService;

    @GetMapping("/list")
    public String list(Model model) {
        List<Question> questionList = this.questionService.getList();
        model.addAttribute("questionList", questionList);

        return "question_list";
    }

    @GetMapping("/detail/{id}")
    public String detail(Model model, @PathVariable("id") Integer id) {
        Question q = this.questionService.getQuestion(id);

        model.addAttribute("question", q);

        return "question_detail";             
    }
}
  • QuestionController.java 클래스명 위에 @RequestMapping("/question") 어노테이션을 추가하면 메서드 안에 @GetMapping을 할 때 /question을 생략하고 그 뒷부분의 URL을 작성하면 된다.

 

[Form 만들기]

  • question detail 페이지에서 답변을 입력하는 Form을 만들고 답변을 등록하기 위한 버튼까지 템플릿에 추가해봅시다.
  • 여기서 말하는 Form(폼)이란 회원가입에서 자주 볼 수 있는 태그의 형식으로서 정보를 제출하기 위한 대화형 컨트롤을 포함하는 문서 구획을 나타낸다.
  • 폼 태그 속성에는 name, action, method, target 등이 있다.
  • action 속성은 폼을 전송할 서버 쪽 스크립트 파일을 지정한다.
  • name 속성은 폼을 식별하기 위한 이름을 지정한다.
  • target 속성은 action에서 지정한 스크립트 파일을 현재 창이 아닌다른 위치에 열도록 지정한다.
  • accept-charset 속성은 폼 전송에 사용할 문자 인코딩을 지정한다.
  • method 속성은 폼을 서버에 전송할 HTTP 메서드를 정한다.
  • HTTP 메서드에는 GET 방식과 POST 방식이 있다.
  • GET 방식은 정보를 요청하기 위해 사용되는 메서드로, URL에 받은 데이터가 고스란히 찍히기 때문에 보안성은 없지만 서버에서 처리할 필요없는 단순한 검색기능이나 링크이동, 브라우저의 주소창 입력 같은 곳에 사용된다.
  • POST 방식은 정보를 생성 및 업데이트하기 위해 서버에 데이터를 보내는 메서드로, URL에 받은 데이터가 찍히지 않기 때문에 보안성을 챙길 수 있어 로그인이나 글 작성/수정, 댓글 작성/수정, 좋아요/취소와 같은 민감한 기능에 사용된다.
  • 아래는 question_detail.html 템플릿에 폼 태그를 추가 작성한 코드이다.
<h2>
    <h3 th:text="${question.id}"></h3>
    <div th:text="${question.subject}"></div>
    <div th:text="${question.content}"></div>
</h2>

<!--입력 폼 만들기-->
<form th:action="@{|/answer/create/${question.id}|}" method="POST">
    <div>
        <span>내용</span>
        <br>
        <textarea name="content" placeholder="내용" rows="5"></textarea>
    </div>

    <button type="submit">답변등록</button>
</form>
  • '답변등록' 이라는 버튼을 누르게 되면 전송되는 Form의 action은 타임리프의 th:action 속성으로 생성한다.
  • 텍스트 창에 답변을 작성하고 버튼을 클릭하면 '/answer/create/id번호' 와 같은 URL이 POST 방식으로 호출된다.
  • 다시 로컬 서버를 실행하여 1번 question의 상세 내용 페이지인 localhost:8090/question/detail/1로 접속하게 되면 아래와 같이 답변 작성이 가능한 폼과 버튼이 생성되어있다.
  • 지금 상황에서 버튼을 누르게 되면 'answer/create/id번호' URL은 아직 매핑이 되어있지 않기 때문에 404 에러가 발생한다.

탬플릿에 추가된 Form 태그

 

[AnswerController.java 생성]

  • 그렇다면 answer 관련 URL을 매핑하기 위해서 답변 관련 컨트롤러를 새로 생성하여 코드를 작성해봅시다.
  • 아래는 AnswerController.java 클래스를 생성하여 새로 작성된 코드이다.
package com.sbs.sbb.Answer;

import com.sbs.sbb.Question.Question;
import com.sbs.sbb.Question.QuestionRepository;
import com.sbs.sbb.Question.QuestionService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@RequestMapping("/answer")         
@Controller
@RequiredArgsConstructor               
public class AnswerController {
    private final QuestionService questionService;

    @PostMapping("/create/{id}")
    public String createAnswer(Model model, @PathVariable("id") Integer id, @RequestParam(value="content") String content) {
        Question q = this.questionService.getQuestion(id);

        return "redirect:/question/detail/%d".formatted(id);
    }
}
  • question_detail.html 에서 '/answer/create/id번호' 와 같은 URL 요청 시 createAnswer() 메서드가 호출되도록 @PostMapping 어노테이션을 사용하여 매핑하였다.
  • POST 요청을 처리하는 경우이기 때문에 @GetMapping이 아닌 @PostMapping을 사용한 점을 잘 봐두면 좋다.
  • createAnswer() 메서드의 매개변수로는 Model 객체와 여러개의 id가 존재하는 것을 생각하여 @PathVariable 어노테이션이 동일하게 사용되었다.
  • 그리고 여기에 @RequestParam(value="content") 와 String content가 추가되었다. 이는 앞서 작성한 question_detail.html 템플릿에서 답변으로 입력한 내용인 name = "content"을 얻으려고 추가하였다. (textarea의 name 속성명과 변수명을 동일하게 생성)
  • 폼을 통해 내용을 작성하고 버튼을 클릭했을 때 정상적으로 데이터가 전송되었다면 return 값으로 해당 id의 question 상세 내용 페이지 URL를 반환하여 리다이렉트하도록 하였다.
  • 실제로 실행하게 되면 버튼 클릭시 localhost:8090/answer/create/1로 작성한 답변 내용이 전달되고, 해당 URL에서 다시 리다이렉트하여 실제 웹 페이지에서는 localhost:8090/question/detail/1 화면이 나타나게 된다.
  • 아래 사진은 실제로 데이터가 주고 받은 로그이며, HTTP 상태 코드가 200번, 300번 대로 성공되는 모습을 볼 수 있다.

데이터 전송 성공

  • 현재 URL인 localhost:8090/question/detail/1 에서 폼의 textarea에 값을 '전송 완료' 로 채우고 답변등록 버튼을 클릭한다.

 

전송한 데이터 확인

  • '전송 완료' 라는 content가 POST 방식으로 localhost:8090/answer/create/1에 요청을 하면 위의 사진에서 볼 수 있듯이 Payload의 content 부분에 '전송 완료'가 제대로 보내진 것을 확인할 수 있다.
  • 브라우저에서는 하얀 화면이 나오는 중이며 이 때, 코드상으로는 AnswerController.java 클래스의 createAnswer() 메서드가 실행된다.
  • 요청 처리는 questionService.getQuestion(id) 메서드를 통해 findById() 메서드를 호출하고 응답으로 localhost:8090/question/detail/1 URL에 리다이렉트하게 된다.

 

리다이렉트 성공

  • 브라우저에서 받은 답변 내용을 랜더링하려 했지만, HTTP 상태 코드가 302라서 헤더에 적힌 URL로 새 요청을 보낸다.
  • 새 요청을 보낼 URL은 localhost:8090/question/detail/1 이고 QuestionController.java 클래스에 의해 detail() 메서드를 실행하고, 요청을 날리기 전과 동일한 화면이 우리 눈에는 보이게 된다. 
반응형