겉바속촉

[점프투스프링부트] 2-11. 답변 등록 본문

IT 일기 (상반기)/SPRING 기초

[점프투스프링부트] 2-11. 답변 등록

겉바속촉 2023. 2. 28. 14:44
728x90
반응형

 

점프투스프링부트 2-11

 

 

 

목표 : 질문에 답변을 등록하고 보여주는 기능 만들기

 

 

 

 

1. 답변 등록 버튼 만들기

질문 상세 템플릿에 답변 저장을 위한 form, textarea, input 엘리먼트를 다음과 같이 추가하자.

 

[파일명:/sbb/src/main/resources/templates/question_detail.html]

<h1 th:text="${question.subject}"></h1>
<div th:text="${question.content}"></div>

<form th:action="@{|/answer/create/${question.id}|}" method="post">
    <textarea name="content" id="content" rows="15"></textarea>
    <input type="submit" value="답변등록">
</form>

 

<답변등록> 버튼을 누르면 전송되는 form의 action은 타임리프의 "th:action" 속성으로 생성한다.

위와 같이 추가하고 질문 상세 페이지에 접속해 보자.

 

 

 

답변을 입력할 수 있는 텍스트창과 <답변등록> 버튼이 생성되었다.

 

이제 <답변등록> 버튼을 누르면 POST 방식으로 /answer/create/<질문id> URL이 호출(submit)될 것이다.

하지만 아직 /answer/create/<질문id> URL에 대한 매핑이 없으므로 버튼을 누르면 다음과 같은 404 페이지가 나타난다.

 

 

 

이 오류를 해결하려면 답변 컨트롤러를 만들고 해당 URL에 대한 매핑을 처리해야 한다.

 

 

 

2. 답변 컨트롤러 만들기

앞에서 질문 컨트롤러를 만들었듯이 답변 컨트롤러를 만들어 보자.

[파일명:/sbb/src/main/java/com/mysite/sbb/answer/AnswerController.java]

package com.mysite.sbb.answer;

import com.mysite.sbb.question.Question;
import com.mysite.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")
@RequiredArgsConstructor
@Controller
public class AnswerController {

    private final QuestionService questionService;

    @PostMapping("/create/{id}")
    public String createAnswer(Model model, @PathVariable("id") Integer id, @RequestParam String content) {
        Question question = this.questionService.getQuestion(id);
        // TODO: 답변을 저장한다. 
        return String.format("redirect:/question/detail/%s", id);
    }
}

 

답변 컨트롤러의 URL 프리픽스도 /answer로 고정했다.

그리고 /answer/create/{id}와 같은 URL 요청시 createAnswer 메서드가 호출되도록 @PostMapping으로 매핑했다. 

 

@PostMapping은 @GetMapping과 동일하게 매핑을 담당하는 역할을 하지만

POST요청만 받아들일 경우에 사용한다.

 

만약 위 URL을 GET방식으로 요청할 경우에는 오류가 발생한다.

@PostMapping(value="/create/{id}") 대신 @PostMapping("/create/{id}") 처럼 value는 생략해도 된다.

 

그리고 createAnswer 메서드의 매개변수에는 @RequestParam String content 항목이 추가되었다.

이 항목은 템플릿에서 답변으로 입력한 내용(content)을 얻기 위해 추가되었다.

템플릿의 답변 내용에 해당하는 textarea의 name 속성명이 content이기 때문에 여기서도 변수명을 content로 사용해야 한다.

만약 content 대신 다른 이름으로 사용하면 오류가 발생할 것이다.

 

createAnswer 메서드의 URL 매핑 /create/{id}에서 {id}는 질문의 id 이므로

이 id 값으로 질문을 조회하고 없을 경우에는 404 오류가 발생할 것이다.

 

하지만 아직 답변을 저장하는 코드를 작성하지 않고 일단 다음과 같은 주석으로 답변을 저장해야 함을 나타내었다.

// TODO: 답변을 저장한다. 

 

 

 

 

3. 답변 저장하기

이제 답변을 저장하는 코드를 작성해 보자.

답변을 저장하려면 답변 서비스가 필요하다.

AnswerService를 다음과 같이 작성하자.

 

[파일명:/sbb/src/main/java/com/mysite/sbb/answer/AnswerService.java]

package com.mysite.sbb.answer;

import com.mysite.sbb.question.Question;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@RequiredArgsConstructor
@Service
public class AnswerService {

    private final AnswerRepository answerRepository;


    public void create(Question question, String content) {
        Answer answer = new Answer();
        answer.setContent(content);
        answer.setCreateDate(LocalDateTime.now());
        answer.setQuestion(question);
        this.answerRepository.save(answer);
    }
}

 

AnswerService에는 답변 생성을 위해 create 메서드를 추가했다.

create 메서드는 입력으로 받은 question과 content를 사용하여 Answer 객체를 생성하여 저장했다.

이제 작성한 create 메서드를 AnswerController에서 사용해 보자.

 

 

[파일명:/sbb/src/main/java/com/mysite/sbb/answer/AnswerController.java]

(... 생략 ...)
public class AnswerController {

    private final QuestionService questionService;
    private final AnswerService answerService;

    @PostMapping("/create/{id}")
    public String createAnswer(Model model, @PathVariable("id") Integer id, @RequestParam String content) {
        Question question = this.questionService.getQuestion(id);
        this.answerService.create(question, content);
        return String.format("redirect:/question/detail/%s", id);
    }
}

 

주석문을 삭제하고 AnswerService의 create 메서드를 호출하여 답변을 저장할수 있게 했다.

 

이제 다시 질문 상세 페이지에 접속하여 텍스트 창에 아무 값이나 입력하고 <답변등록>을 눌러 보자.

답변은 잘 저장되었다.

하지만 여전히 화면에는 아무런 변화가 없다. 왜 그럴까?

 

왜냐하면 우리는 아직 등록한 답변을 템플릿에 표시하는 기능을 추가하지 않았기 때문이다.

 

 

 

4. 질문 상세 페이지에 답변 표시하기

 

질문에 등록된 답변을 상세 화면에 표시해 보자.

답변은 등록된 질문 밑에 보여야 하므로 질문 상세 템플릿에 다음의 코드를 추가하자.

 

[파일명:/sbb/src/main/resources/templates/question_detail.html]

<h1 th:text="${question.subject}"></h1>
<div th:text="${question.content}"></div>
<h5 th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
<div>
    <ul>
        <li th:each="answer : ${question.answerList}" th:text="${answer.content}"></li>
    </ul>
</div>
<form th:action="@{|/answer/create/${question.id}|}" method="post">
    <textarea name="content" id="content" rows="15"></textarea>
    <input type="submit" value="답변등록">
</form>

 

기존 코드에서 답변을 확인할 수 있는 영역을 추가했다.

 

 #lists.size(question.answerList)} 👉 답변 개수를 의미한다.

 #lists.size(이터러블객체) 👉타임리프가 제공하는 유틸리티로 객체의 길이를 반환한다.

 

답변은 question 객체의 answerList를 순회하여 <li> 엘리먼트로 표시했다.

이제 질문 상세 페이지를 새로 고침하면 방금 등록한 답변들이 보일 것이다.

 

 

정말 너무 어렵당.......ㅎ

 

 

 

 

728x90
반응형