Skip to main content

02J. ์งˆ๋ฌธ ์ƒ์„ธ

About 2 minJavaSpringAWScrashcoursejavajdkjdk8streamspringspringframeworkspringbootawsaws-ec2

02J. ์งˆ๋ฌธ ์ƒ์„ธ ๊ด€๋ จ


2-10. ์งˆ๋ฌธ ์ƒ์„ธ

์ ํ”„ ํˆฌ ์Šคํ”„๋ง๋ถ€ํŠธ - WikiDocs

pahkey/sbb3 - 2-10open in new window

์ด๋ฒˆ ์žฅ์—์„œ๋Š” ์งˆ๋ฌธ ๋ชฉ๋ก์— ์ด์–ด ์งˆ๋ฌธ ์ƒ์„ธ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ด ๋ณด์ž.


์งˆ๋ฌธ ์ƒ์„ธ ๋งํฌ ์ถ”๊ฐ€ํ•˜๊ธฐ

๋จผ์ € ์งˆ๋ฌธ ๋ชฉ๋ก์˜ ์ œ๋ชฉ์„ ํด๋ฆญํ–ˆ์„๋•Œ ์ƒ์„ธํ™”๋ฉด์ด ํ˜ธ์ถœ๋˜๋„๋ก ์ œ๋ชฉ์— ๋งํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜์ž. ์งˆ๋ฌธ ๋ชฉ๋ก ํ…œํ”Œ๋ฆฟ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/resources/templates/question_list.html

<table>
    <thead>
        <tr>
            <th>์ œ๋ชฉ</th>
            <th>์ž‘์„ฑ์ผ์‹œ</th>
        </tr>
    </thead>
    <tbody>
        <tr th:each="question, index : ${questionList}">
            <td>
                <a th:href="@{|/question/detail/${question.id}|}" th:text="${question.subject}"></a>
            </td>
            <td th:text="${question.createDate}"></td>
        </tr>
    </tbody>
</table>









ย 
ย 
ย 




์ œ๋ชฉ์„ <td> ์—˜๋ฆฌ๋จผํŠธ์˜ ํ…์ŠคํŠธ๋กœ ์ถœ๋ ฅํ•˜๋˜ ๊ฒƒ์—์„œ ๋งํฌ๋กœ ๋ณ€๊ฒฝํ–ˆ๋‹ค. ํƒ€์ž„๋ฆฌํ”„์—์„œ ๋งํฌ์˜ ์ฃผ์†Œ๋Š” th:href ์†์„ฑ์„ ์‚ฌ์šฉํ•œ๋‹ค. ํƒ€์ž„๋ฆฌํ”„์—์„œ th:href ์ฒ˜๋Ÿผ URL ์ฃผ์†Œ๋ฅผ ๋‚˜ํƒ€๋‚ผ๋•Œ๋Š” ๋ฐ˜๋“œ์‹œ @{ ๋ฌธ์ž์™€ } ๋ฌธ์ž ์‚ฌ์ด์— ์ž…๋ ฅํ•ด์•ผ ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  URL ์ฃผ์†Œ๋Š” ๋ฌธ์ž์—ด /question/detail/๊ณผ ${question.id} ๊ฐ’์ด ์กฐํ•ฉ๋˜์–ด /question/detail/${question.id}๋กœ ๋งŒ๋“ค์–ด์กŒ๋‹ค. ์ด๋•Œ ์ขŒ์šฐ์— | ๋ฌธ์ž์—†์ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

<a th:href="@{/question/detail/${question.id}}" th:text="${question.subject}"></a>

/question/detail/๊ณผ ๊ฐ™์€ ๋ฌธ์ž์—ด๊ณผ ${question.id}์™€ ๊ฐ™์€ ์ž๋ฐ” ๊ฐ์ฒด์˜ ๊ฐ’์„ ๋”ํ•  ๋•Œ๋Š” ๋ฐ˜๋“œ์‹œ ๋‹ค์Œ์ฒ˜๋Ÿผ | ๊ณผ | ๊ธฐํ˜ธ๋กœ ์ขŒ์šฐ๋ฅผ ๊ฐ์‹ธ ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

<a th:href="@{|/question/detail/${question.id}|}" th:text="${question.subject}"></a>

ํƒ€์ž„๋ฆฌํ”„๋Š” ๋ฌธ์ž์—ด์„ ์—ฐ๊ฒฐ(concatenation)ํ•  ๋•Œ | ๋ฌธ์ž๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.


์งˆ๋ฌธ ์ƒ์„ธ ์ปจํŠธ๋กค๋Ÿฌ ๋งŒ๋“ค๊ธฐ

์ด์ œ ์งˆ๋ฌธ ๋ชฉ๋ก ํŽ˜์ด์ง€์— ์ ‘์†ํ•˜์—ฌ ๋งํฌ๋ฅผ ํด๋ฆญํ•ด ๋ณด์ž.

์•„๋งˆ๋„ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ์ด๋‹ค.
์•„๋งˆ๋„ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ์ด๋‹ค.

http://localhost:8080/question/detail/2 URL ์š”์ฒญ์— ๋Œ€ํ•œ ๋งคํ•‘์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— 404(Page not found) ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ์˜ค๋ฅ˜๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์งˆ๋ฌธ ์ƒ์„ธ ํŽ˜์ด์ง€์— ๋Œ€ํ•œ URL ๋งคํ•‘์„ QuestionController์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ถ”๊ฐ€ํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/java/com/mysite/sbb/question/QuestionController.java

// (... ์ƒ๋žต ...)
import org.springframework.web.bind.annotation.PathVariable;
// (... ์ƒ๋žต ...)
public class QuestionController {

    (... ์ƒ๋žต ...)

    @GetMapping(value = "/question/detail/{id}")
    public String detail(Model model, @PathVariable("id") Integer id) {
        return "question_detail";
    }
}

ย 





ย 
ย 
ย 
ย 

์š”์ฒญ URL http://localhost:8080/question/detail/2์˜ ์ˆซ์ž 2์ฒ˜๋Ÿผ ๋ณ€ํ•˜๋Š” id ๊ฐ’์„ ์–ป์„ ๋•Œ์—๋Š” ์œ„์™€ ๊ฐ™์ด @PathVariable ์• ๋„ˆํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. ์ด ๋•Œ @GetMapping(value = "/question/detail/{id}") ์—์„œ ์‚ฌ์šฉํ•œ id์™€ @PathVariable("id")์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ์ด๋ฆ„์ด ๋™์ผํ•ด์•ผ ํ•œ๋‹ค.

์œ„์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•˜๊ณ  ๋‹ค์‹œ URL์„ ํ˜ธ์ถœํ•˜๋ฉด ์ด๋ฒˆ์—๋Š” 404 ๋Œ€์‹  500 ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ์ด๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์‘๋‹ต์œผ๋กœ ๋ฆฌํ„ดํ•œ question_detail ํ…œํ”Œ๋ฆฟ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์ด question_detail.html ํŒŒ์ผ์„ ์‹ ๊ทœ๋กœ ์ž‘์„ฑํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/resources/templates/question_detail.html

<h1>์ œ๋ชฉ</h1>
<div>๋‚ด์šฉ</div>
๊ทธ๋Ÿฌ๋ฉด ์ด์ œ ์˜ค๋ฅ˜์—†์ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ™”๋ฉด์ด ๋‚˜ํƒ€๋‚  ๊ฒƒ์ด๋‹ค.
๊ทธ๋Ÿฌ๋ฉด ์ด์ œ ์˜ค๋ฅ˜์—†์ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ™”๋ฉด์ด ๋‚˜ํƒ€๋‚  ๊ฒƒ์ด๋‹ค.

์„œ๋น„์Šค

์ด์ œ ํ™”๋ฉด์— ์ถœ๋ ฅํ•œ "์ œ๋ชฉ", "๋‚ด์šฉ" ๋ฌธ์ž์—ด ๋Œ€์‹  ๋ฐ์ดํ„ฐ์˜ ์‹ค์ œ ์ œ๋ชฉ๊ณผ ๋‚ด์šฉ์„ ์ถœ๋ ฅํ•ด ๋ณด์ž. Question ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๊ธฐ ์œ„ํ•ด์„œ QuestionService๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/java/com/mysite/sbb/question/QuestionService.java

// (... ์ƒ๋žต ...)
import java.util.Optional;
import com.mysite.sbb.DataNotFoundException;
// (... ์ƒ๋žต ...)
public class QuestionService {

    // (... ์ƒ๋žต ...)

    public Question getQuestion(Integer id) {  
        Optional<Question> question = this.questionRepository.findById(id);
        if (question.isPresent()) {
            return question.get();
        } else {
            throw new DataNotFoundException("question not found");
        }
    }
}

ย 
ย 





ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

id ๊ฐ’์œผ๋กœ Question ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๋Š” getQuestion ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค. ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ๋กœ ์–ป์€ Question ๊ฐ์ฒด๋Š” Optional ๊ฐ์ฒด์ด๊ธฐ ๋•Œ๋ฌธ์— ์œ„์™€ ๊ฐ™์ด isPresent ๋ฉ”์„œ๋“œ๋กœ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ๊ฒ€์‚ฌํ•˜๋Š” ๋กœ์ง์ด ํ•„์š”ํ•˜๋‹ค. ๋งŒ์•ฝ id ๊ฐ’์— ํ•ด๋‹นํ•˜๋Š” Question ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์„ ๊ฒฝ์šฐ์—๋Š” DataNotFoundException์„ ๋ฐœ์ƒ์‹œํ‚ค๋„๋ก ํ–ˆ๋‹ค. DataNotFoundException ํด๋ž˜์Šค๋Š” ์•„์ง ์กด์žฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ปดํŒŒ์ผ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ์ด๋‹ค. DataNotFoundException ํด๋ž˜์Šค๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/java/com/mysite/sbb/DataNotFoundException.java

package com.mysite.sbb;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "entity not found")
public class DataNotFoundException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    public DataNotFoundException(String message) {
        super(message);
    }
}

DataNotFoundException์€ RuntimeException์„ ์ƒ์†ํ•˜์—ฌ ๋งŒ๋“ค์—ˆ๋‹ค. ๋งŒ์•ฝ DataNotFoundException์ด ๋ฐœ์ƒํ•˜๋ฉด @ResponseStatus ์• ๋„ˆํ…Œ์ด์…˜์— ์˜ํ•ด 404 ์˜ค๋ฅ˜(HttpStatus.NOT_FOUND)๊ฐ€ ๋‚˜ํƒ€๋‚  ๊ฒƒ์ด๋‹ค.

๊ทธ๋ฆฌ๊ณ  QuestionController์—์„œ QuestionService์˜ getQuestion ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ Question ๊ฐ์ฒด๋ฅผ ํ…œํ”Œ๋ฆฟ์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/java/com/mysite/sbb/question/QuestionController.java

// (... ์ƒ๋žต ...)
public class QuestionController {

    // (... ์ƒ๋žต ...)

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







ย 
ย 



ํ…œํ”Œ๋ฆฟ

QuestionController์˜ detail ๋ฉ”์„œ๋“œ์—์„œ Model ๊ฐ์ฒด์— "question" ์ด๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ Question ๊ฐ์ฒด๋ฅผ ์ €์žฅํ–ˆ์œผ๋ฏ€๋กœ ํ…œํ”Œ๋ฆฟ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

ํŒŒ์ผ๋ช…: /sbb/src/main/resources/templates/question_detail.html

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

์งˆ๋ฌธ ์ƒ์„ธ ํ™•์ธํ•˜๊ธฐ

์ด์ œ ๋‹ค์‹œ ์งˆ๋ฌธ ์ƒ์„ธ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•ด ๋ณด์ž.

๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ™”๋ฉด์ด ๋‚˜ํƒ€๋‚  ๊ฒƒ์ด๋‹ค.
๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ™”๋ฉด์ด ๋‚˜ํƒ€๋‚  ๊ฒƒ์ด๋‹ค.

์กฐํšŒํ•œ Question ๋ฐ์ดํ„ฐ์˜ ์ œ๋ชฉ๊ณผ ๋‚ด์šฉ์ด ํ™”๋ฉด์— ์ž˜ ์ถœ๋ ฅ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฒˆ์—๋Š” ๋‹ค์Œ์ฒ˜๋Ÿผ 33๊ณผ ๊ฐ™์€ ์กด์žฌํ•˜์ง€ ์•Š๋Š” id ๊ฐ’์œผ๋กœ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•ด ๋ณด์ž.

http://localhost:8080/question/detail/33
๋‹ค์Œ์ฒ˜๋Ÿผ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๋ ค๊ณ  ํ•  ๊ฒฝ์šฐ์—๋Š” 404 Not found ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
๋‹ค์Œ์ฒ˜๋Ÿผ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๋ ค๊ณ  ํ•  ๊ฒฝ์šฐ์—๋Š” 404 Not found ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

URL ํ”„๋ฆฌํ”ฝ์Šค(prefix)

๋‹ค์Œ์œผ๋กœ ๋„˜์–ด๊ฐ€๊ธฐ ์ „์— QuestionController์˜ URL ๋งคํ•‘์„ ์ž ์‹œ ์‚ดํŽด๋ณด์ž. ํ˜„์žฌ QuestionController์—๋Š” ๋‹ค์Œ 2๊ฐœ์˜ URL ๋งคํ•‘์ด ์žˆ๋‹ค.

  • @GetMapping("/question/list")
  • @GetMapping(value = "/question/detail/{id}")

URL ๋งคํ•‘์‹œ value ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์ƒ๋žตํ• ์ˆ˜ ์žˆ๋‹ค.

URL์˜ ํ”„๋ฆฌํ”ฝ์Šค๊ฐ€ ๋ชจ๋‘ /question์œผ๋กœ ์‹œ์ž‘ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์ˆ˜ ์žˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ ํด๋ž˜์Šค๋ช… ์œ„์— @RequestMapping("/question") ์• ๋„ˆํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๊ณ  ๋ฉ”์„œ๋“œ ๋‹จ์œ„์—์„œ๋Š” /question ๋ฅผ ์ƒ๋žตํ•œ ๊ทธ ๋’ท ๋ถ€๋ถ„๋งŒ์„ ์ ์œผ๋ฉด ๋œ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด QuestionController๋ฅผ ์ˆ˜์ •ํ•ด ๋ณด์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/java/com/mysite/sbb/question/QuestionController.java

// (... ์ƒ๋žต ...)
import org.springframework.web.bind.annotation.RequestMapping;
// (... ์ƒ๋žต ...)

@RequestMapping("/question")
@RequiredArgsConstructor
@Controller
public class QuestionController {

    private final QuestionService questionService;

    @GetMapping("/list")
    public String list(Model model) {
        (... ์ƒ๋žต ...)
    }

    @GetMapping(value = "/detail/{id}")
    public String detail(Model model, @PathVariable("id") Integer id) {
        (... ์ƒ๋žต ...)
    }
}

ย 


ย 






ย 




ย 




list ๋ฉ”์„œ๋“œ์˜ URL ๋งคํ•‘์€ /list ์ด์ง€๋งŒ ํด๋ž˜์Šค์— /question์ด๋ผ๋Š” URL ๋งคํ•‘์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— /question + /list๊ฐ€ ๋˜์–ด ์ตœ์ข…์ ์ธ URL ๋งคํ•‘์€ /question/list๊ฐ€ ๋œ๋‹ค. ์œ„์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•˜๋ฉด ๊ธฐ์กด๊ณผ ์™„์ „ํžˆ ๋™์ผํ•œ ๊ธฐ์ค€์œผ๋กœ URL ๋งคํ•‘์ด ์ด๋ฃจ์–ด ์ง„๋‹ค. ๋‹ค๋งŒ, ์•ž์œผ๋กœ QuestionController์—์„œ ์‚ฌ์šฉํ•˜๋Š” URL ๋งคํ•‘์€ ํ•ญ์ƒ /question ์œผ๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•˜๋Š” ๊ทœ์น™์ด ์ƒ๊ธด ๊ฒƒ์ด๋‹ค.

์ปจํŠธ๋กค๋Ÿฌ์˜ ํด๋ž˜์Šค ๋‹จ์œ„์˜ URL ๋งคํ•‘์€ ํ•„์ˆ˜์‚ฌํ•ญ์ด ์•„๋‹ˆ๋‹ค. ์ปจํŠธ๋กค๋Ÿฌ์˜ ์„ฑ๊ฒฉ์— ๋งž๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.


์ด์ฐฌํฌ (MarkiiimarK)
Never Stop Learning.