02G. ์ง๋ฌธ ๋ชฉ๋ก๊ณผ ํ ํ๋ฆฟ
02G. ์ง๋ฌธ ๋ชฉ๋ก๊ณผ ํ ํ๋ฆฟ ๊ด๋ จ
์ด๋ฒ ์ฅ์์๋ SBB์ ํต์ฌ ๊ธฐ๋ฅ์ธ ์ง๋ฌธ ๋ชฉ๋ก์ ๊ตฌํํ ๊ฒ์ด๋ค. ๊ทธ๋ผ ์์ํด ๋ณด์.
์ฐ๋ฆฌ๊ฐ ์ํ๋ ์ง๋ฌธ ๋ชฉ๋ก์ ๋ค์ ์ฃผ์์ ์ ์ํ ๋ ๋์ํด์ผ ํ๋ค. ๋ก์ปฌ ์๋ฒ๋ฅผ ์คํํ๊ณ ์น ๋ธ๋ผ์ฐ์ ์์ http://localhost:8080/question/list
์ ์ ์ํด ๋ณด์.
404 ์ค๋ฅ ํด๊ฒฐํ๊ธฐ
404 ์ค๋ฅ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด /question/list
URL์ ๋ํ ๋งคํ์ด ์๋ ์ปจํธ๋กค๋ฌ๊ฐ ํ์ํ๋ค. QuestionController.java
ํ์ผ์ ๋ค์๊ณผ ๊ฐ์ด ์ ๊ท ์์ฑํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/question
QuestionController.java
package com.mysite.sbb.question;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class QuestionController {
@GetMapping("/question/list")
@ResponseBody
public String list() {
return "question list";
}
}
์ด๋ ๊ฒ ์์ ํ๊ณ ๋ค์ http://localhost:8080/question/list
์ ์ ์ํ๋ฉด ํ๋ฉด์ "question list" ๋ฌธ์์ด์ด ์ถ๋ ฅ๋ ๊ฒ์ด๋ค.
ํ ํ๋ฆฟ ์ค์ ํ๊ธฐ
ํ์ง๋ง ๋ณดํต ๋ธ๋ผ์ฐ์ ์ ์๋ตํ๋ ๋ฌธ์์ด์ ์์ ์์ฒ๋ผ ์๋ฐ ์ฝ๋์์ ์ง์ ๋ง๋ค์ง๋ ์๋๋ค.
์์์๋ "question list" ๋ผ๋ ๋ฌธ์์ด์ ์ง์ ์๋ฐ ์ฝ๋๋ก ์์ฑํ์ฌ ๋ธ๋ผ์ฐ์ ์ ๋ฆฌํดํ๋ค.
์ผ๋ฐ์ ์ผ๋ก ๋ง์ด ์ฌ์ฉํ๋ ๋ฐฉ์์ ํ ํ๋ฆฟ ๋ฐฉ์์ด๋ค. ํ ํ๋ฆฟ์ ์๋ฐ ์ฝ๋๋ฅผ ์ฝ์ ํ ์ ์๋ HTML ํ์์ ํ์ผ์ด๋ค.
ํ ํ๋ฆฟ์ ์ด๋ป๊ฒ ์ฌ์ฉํ ์ ์๋์ง ์์๋ณด์. ์คํ๋ง๋ถํธ์์ ์ฌ์ฉํ ์ ์๋ ํ ํ๋ฆฟ ์์ง์๋ Thymeleaf, Mustache, Groovy, Freemarker, Velocity ๋ฑ์ด ์๋ค. ์ด ์ฑ ์์๋ ์คํ๋ง ์ง์์์ ์ถ์ฒํ๋ ํ์๋ฆฌํ(Thymleaf) ํ ํ๋ฆฟ ์์ง์ ์ฌ์ฉํ ๊ฒ์ด๋ค.
- ํ์๋ฆฌํ - https://www.thymeleaf.org
ํ์๋ฆฌํ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ์ค์น๊ฐ ํ์ํ๋ค. ๋ค์๊ณผ ๊ฐ์ด build.gradle
ํ์ผ์ ์์ ํ์.
ํ์ผ๋ช :
/sbb/
build.gradle
// (... ์๋ต ...)
dependencies {
// (... ์๋ต ...)
implementation "org.springframework.boot:spring-boot-starter-thymeleaf"
implementation "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect"
}
// (... ์๋ต ...)
์์ ๊ฐ์ด ์์ ํ๊ณ ["Refresh Gradle Project"]
๋ก ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํ์.
ํ์๋ฆฌํ ํ ํ๋ฆฟ ์์ง์ ์ ์ฉํ๊ธฐ ์ํด์๋ ๋ก์ปฌ์๋ฒ ์ฌ์์์ด ํ์ํ๋ค. ๋ก์ปฌ์๋ฒ๋ฅผ ๋ฐ๋์ ์ฌ์์ํ๊ณ ์ดํ ๊ณผ์ ์ ์งํํ์.
ํ ํ๋ฆฟ ์ฌ์ฉํ๊ธฐ
.question_list.html
ํ์ผ์ ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ์.
ํ์ผ๋ช :
/sbb/src/main/resources/templates/
question_list.html
<h2>Hello Template</h2>
question_list.html
ํ์ผ์ด ๋ฐ๋ก ํ
ํ๋ฆฟ ํ์ผ์ด๋ค.
๊ทธ๋ฆฌ๊ณ QuestionController
๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ์.
package com.mysite.sbb.question;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class QuestionController {
@GetMapping("/question/list")
// @ResponseBody
public String list() {
return "question_list";
}
}
๋ฆฌํด ๋ฌธ์์ด์ "question list"๊ฐ ์๋ "question_list" ์์ ์ฃผ์ํ์.
ํ
ํ๋ฆฟ์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ๊ธฐ์กด์ ์ฌ์ฉํ๋ @ResponseBody
์ ๋ํ
์ด์
์ ํ์์์ผ๋ฏ๋ก ์ญ์ ํ๋ค. ๊ทธ๋ฆฌ๊ณ list ๋ฉ์๋์์ question_list.html
ํ
ํ๋ฆฟ ํ์ผ์ ์ด๋ฆ์ธ "question_list
"๋ฅผ ๋ฆฌํดํ๋ค.
๊ทธ๋ฆฌ๊ณ ๋ค์ http://localhost:8080/question/list
์ ์ ์ํด ๋ณด์.
๋ฐ์ดํฐ ์กฐํํ์ฌ ํ ํ๋ฆฟ์ ์ ๋ฌํ๊ธฐ
ํ ํ๋ฆฟ์ ๋ด์ฉ์ ํ๋ฉด์ ์ ๋ฌํ๋ ๊ฒ์ ์ฑ๊ณตํ๋ค. ์ด์ ํ ํ๋ฆฟ์ ์ง๋ฌธ ๋ชฉ๋ก์ ์กฐํํ์ฌ ์ถ๋ ฅํด ๋ณด์. ์ง๋ฌธ ๋ชฉ๋ก์ ์กฐํํ๊ธฐ ์ํด์๋ Question ๋ฆฌํฌ์งํฐ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค. Question ๋ฆฌํฌ์งํฐ๋ฆฌ๋ก ์กฐํํ ์ง๋ฌธ ๋ชฉ๋ก์ Model ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ ํ ํ๋ฆฟ์ ์ ๋ฌํ ์ ์๋ค. ์์ ๋ฅผ ํตํด ์์๋ณด์.
๋ค์๊ณผ ๊ฐ์ด QuestionController
๋ฅผ ์์ ํ์.
ํ์ผ๋ช :
/sbb/src/main/java/com/mysite/sbb/question/
QuestionController.java
package com.mysite.sbb.question;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Controller
public class QuestionController {
private final QuestionRepository questionRepository;
@GetMapping("/question/list")
public String list(Model model) {
List<Question> `questionList` = this.questionRepository.findAll();
model.addAttribute("`questionList`", `questionList`);
return "question_list";
}
}
์ฐ์ @RequiredArgsConstructor
์ ๋ํ
์ด์
์ผ๋ก questionRepository
์์ฑ์ ํฌํจํ๋ ์์ฑ์๋ฅผ ์์ฑํ์๋ค. @RequiredArgsConstructor
๋ ๋กฌ๋ณต์ด ์ ๊ณตํ๋ ์ ๋ํ
์ด์
์ผ๋ก final์ด ๋ถ์ ์์ฑ์ ํฌํจํ๋ ์์ฑ์๋ฅผ ์๋์ผ๋ก ์์ฑํ๋ ์ญํ ์ ํ๋ค. ๋กฌ๋ณต์ @Getter
, @Setter
๊ฐ ์๋์ผ๋ก Getter, Setter ๋ฉ์๋๋ฅผ ์์ฑํ๋ ๊ฒ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก @RequiredArgsConstructor
๋ ์๋์ผ๋ก ์์ฑ์๋ฅผ ์์ฑํ๋ค. ๋ฐ๋ผ์ ์คํ๋ง ์์กด์ฑ ์ฃผ์
๊ท์น์ ์ํด questionRepository
๊ฐ์ฒด๊ฐ ์๋์ผ๋ก ์ฃผ์
๋๋ค.
์คํ๋ง์ ์์กด์ฑ ์ฃผ์ (Dependency Injection) ๋ฐฉ์ 3๊ฐ์ง
@Autowired
์์ฑ: ์์ฑ์@Autowired
์ ๋ํ ์ด์ ์ ์ ์ฉํ์ฌ ๊ฐ์ฒด๋ฅผ ์ฃผ์ ํ๋ ๋ฐฉ์- ์์ฑ์: ์์ฑ์๋ฅผ ์์ฑํ์ฌ ๊ฐ์ฒด๋ฅผ ์ฃผ์ ํ๋ ๋ฐฉ์ (๊ถ์ฅํ๋ ๋ฐฉ์)
- Setter: Setter ๋ฉ์๋๋ฅผ ์์ฑํ์ฌ ๊ฐ์ฒด๋ฅผ ์ฃผ์
ํ๋ ๋ฐฉ์ (๋ฉ์๋์
@Autowired
์ ๋ํ ์ด์ ์ ์ฉ์ด ํ์ํ๋ค.)
ํ ์คํธ์ฝ๋(
SbbApplicationTests.java
)์์๋ ์์ฑ์@Autowired
์ ๋ํ ์ด์ ์ ์ฌ์ฉํ์ฌ ๊ฐ์ฒด๋ฅผ ์ฃผ์ ํ๋ค.
๊ทธ๋ฆฌ๊ณ Question ๋ฆฌํฌ์งํฐ์ findAll ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ง๋ฌธ ๋ชฉ๋ก ๋ฐ์ดํฐ์ธ questionList
๋ฅผ ์์ฑํ๊ณ Model ๊ฐ์ฒด์ "questionList
" ๋ผ๋ ์ด๋ฆ์ผ๋ก ๊ฐ์ ์ ์ฅํ๋ค. Model ๊ฐ์ฒด๋ ์๋ฐ ํด๋์ค์ ํ
ํ๋ฆฟ ๊ฐ์ ์ฐ๊ฒฐ๊ณ ๋ฆฌ ์ญํ ์ ํ๋ค. Model ๊ฐ์ฒด์ ๊ฐ์ ๋ด์๋๋ฉด ํ
ํ๋ฆฟ์์ ๊ทธ ๊ฐ์ ์ฌ์ฉํ ์ ์๋ค.
Model ๊ฐ์ฒด๋ ๋ฐ๋ก ์์ฑํ ํ์์์ด ์ปจํธ๋กค๋ฌ ๋ฉ์๋์ ๋งค๊ฐ๋ณ์๋ก ์ง์ ํ๊ธฐ๋ง ํ๋ฉด ์คํ๋ง๋ถํธ๊ฐ ์๋์ผ๋ก Model ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
ํ ํ๋ฆฟ์์ ์ ๋ฌ๋ฐ์ ๋ฐ์ดํฐ ์ฌ์ฉํ๊ธฐ
Model ๊ฐ์ฒด์ ์ ์ฅํ ๊ฐ์ ํ ํ๋ฆฟ์์ ์ฌ์ฉํ ์ ์๋ค๊ณ ํ๋ค. ์ด๋ป๊ฒ ์ฌ์ฉํ ์ ์์๊น? ๋ค์๊ณผ ๊ฐ์ด question_list.html ํ ํ๋ฆฟ์ ์์ ํด ๋ณด์.
ํ์ผ๋ช :
/sbb/src/main/resources/templates/
question_list.html
<table>
<thead>
<tr>
<th>์ ๋ชฉ</th>
<th>์์ฑ์ผ์</th>
</tr>
</thead>
<tbody>
<tr th:each="question : ${questionList}">
<td th:text="${question.subject}"></td>
<td th:text="${question.createDate}"></td>
</tr>
</tbody>
</table>
์ง๋ฌธ ๋ชฉ๋ก์ HTML์ ํ ์ด๋ธ ๊ตฌ์กฐ๋ก ํ์๋๊ฒ ํ์๋ค.
ํ
ํ๋ฆฟ ํ์ผ์ ์
๋ ฅ๋ th:each="question : ${questionList}"
์ ๊ฐ์ ํน์ดํ ํํ์ด ๋์๋ ๊ฒ์ด๋ค. th:
๋ก ์์ํ๋ ์์ฑ์ ํ์๋ฆฌํ ํ
ํ๋ฆฟ ์์ง์ด ์ฌ์ฉํ๋ ์์ฑ์ด๋ค. ๋ฐ๋ก ์ด ๋ถ๋ถ์ด ์๋ฐ ์ฝ๋์ ์ฐ๊ฒฐ๋๋ค. question_list.html
ํ์ผ์ ์ฌ์ฉํ ํ์๋ฆฌํ ์์ฑ๋ค์ ์ ์ ์ดํด๋ณด์.
<tr th:each="question : ${`questionList`}">
QuestionController
์ list ๋ฉ์๋์์ ์กฐํํ ์ง๋ฌธ ๋ชฉ๋ก ๋ฐ์ดํฐ๋ฅผ "questionList
"๋ผ๋ ์ด๋ฆ์ผ๋ก Model ๊ฐ์ฒด์ ์ ์ฅํ๋ค. ํ์๋ฆฌํ๋ Model ๊ฐ์ฒด์ ์ ์ฅ๋ ๊ฐ์ ์ฝ์ ์ ์์ผ๋ฏ๋ก ํ
ํ๋ฆฟ์์ questionList
๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ๋๋ ๊ฒ์ด๋ค. ์์ ์ฝ๋๋ <tr> ... </tr>
์๋ฆฌ๋จผํธ๋ฅผ questionList
์ ๊ฐฏ์๋งํผ ๋ฐ๋ณตํ์ฌ ์ถ๋ ฅํ๋ ์ญํ ์ ํ๋ค. ๊ทธ๋ฆฌ๊ณ questionList
์ ์ ์ฅ๋ ๋ฐ์ดํฐ๋ฅผ ํ๋์ฉ ๊บผ๋ด question ๊ฐ์ฒด์ ๋์
ํ์ฌ ๋ฐ๋ณต๊ตฌ๊ฐ ๋ด์์ ์ฌ์ฉํ ์ ์๊ฒ ํ๋ค. ์๋ฐ์ for each ๋ฌธ์ ๋ ์ฌ๋ฆฌ๋ฉด ์ฝ๊ฒ ์ดํดํ ์ ์์ ๊ฒ์ด๋ค.
๋ค์ ์ฝ๋๋ ๋ฐ๋ก ์์ for ๋ฌธ์์ ์ป์ question
๊ฐ์ฒด์ ์ ๋ชฉ์ <td>
์๋ฆฌ๋จผํธ์ ํ
์คํธ๋ก ์ถ๋ ฅํ๋ค.
<td th:text="${question.subject}"></td>
๋ค์ ์ฝ๋๋ ๊ฐ์ ๋งฅ๋ฝ์ผ๋ก ์ดํดํ ์ ์๋ค.
<td th:text="${question.createDate}"></td>
์ด์ ๋ธ๋ผ์ฐ์ ์์ ๋ค์ http://localhost:8080/question/list
์ ์ ์ํด ๋ณด์.
์ด์ ์ ํ ์คํธ๋ก ๋ฑ๋กํ ์ง๋ฌธ 1๊ฑด์ด ์กฐํ๋ ๋ชจ์ต์ด๋ค. ๋ง์ฝ ํ ์คํธ์ Question ๋ฐ์ดํฐ๋ฅผ ๋ ์ถ๊ฐํ๋ค๋ฉด ๋ ๋ง์ ์ง๋ฌธ์ด ํ์๋ ๊ฒ์ด๋ค.
์์ฃผ ์ฌ์ฉํ๋ ํ์๋ฆฌํ์ ์์ฑ
ํ์๋ฆฌํ์ ์์ฃผ ์ฌ์ฉํ๋ ์์ฑ์๋ ๋ค์ 3๊ฐ์ง ์ ํ์ด ์๋ค. ์ด 3๊ฐ์ง ์ ํ๋ง ์์๋ ์ฌ๋ฌ ๊ธฐ๋ฅ์ ์ถฉ๋ถํ ๋ง๋ค ์ ์๋ค.
1. ๋ถ๊ธฐ๋ฌธ ์์ฑ
๋ถ๊ธฐ๋ฌธ ์์ฑ์ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ๋ค.
th:if="${question != null}"
์์ ๊ฒฝ์ฐ question ๊ฐ์ฒด๊ฐ null ์ด ์๋ ๊ฒฝ์ฐ์ ํด๋น ์๋ฆฌ๋จผํธ๊ฐ ํ์๋๋ค.
2. ๋ฐ๋ณต๋ฌธ ์์ฑ
๋ฐ๋ณต๋ฌธ์ ๋ฐ๋ณตํ์๋งํผ ํด๋น ์๋ฆฌ๋จผํธ๋ฅผ ๋ฐ๋ณตํ์ฌ ํ์ํ๋ค. ๋ฐ๋ณต๋ฌธ ์์ฑ์ ์๋ฐ์ for each ๋ฌธ๊ณผ ์ ์ฌํ๋ค.
th:each="question : ${`questionList`}"
๋ฐ๋ณต๋ฌธ์ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ ์๋ ์๋ค.
th:each="question, loop : ${`questionList`}"
์ถ๊ฐํ loop ๊ฐ์ฒด๋ฅผ ์ด์ฉํ์ฌ ๋ฃจํ ๋ด์์ ๋ค์๊ณผ ๊ฐ์ ์์ฑ์ ์ฌ์ฉํ ์ ์๋ค.
loop.index
: ๋ฐ๋ณต ์์, 0๋ถํฐ 1์ฉ ์ฆ๊ฐloop.count
: ๋ฐ๋ณต ์์, 1๋ถํฐ 1์ฉ ์ฆ๊ฐloop.size
: ๋ฐ๋ณต ๊ฐ์ฒด์ ์์ ๊ฐฏ์ (์:questionList
์ ์์ ๊ฐฏ์)loop.first
: ๋ฃจํ์ ์ฒซ๋ฒ์งธ ์์์ธ ๊ฒฝ์ฐtrue
loop.last
: ๋ฃจํ์ ๋ง์ง๋ง ์์์ธ ๊ฒฝ์ฐtrue
loop.odd
: ๋ฃจํ์ ํ์๋ฒ์งธ ์์์ธ ๊ฒฝ์ฐtrue
loop.even
: ๋ฃจํ์ ์ง์๋ฒ์งธ ์์์ธ ๊ฒฝ์ฐtrue
loop.current
: ํ์ฌ ๋์ ๋ ๊ฐ์ฒด (์: ์์ ๊ฒฝ์ฐquestion
๊ณผ ๋์ผ)
3. ํ ์คํธ ์์ฑ
th:text=๊ฐ
์์ฑ์ ํด๋น ์๋ฆฌ๋จผํธ์ ํ
์คํธ๋ก "๊ฐ"์ ์ถ๋ ฅํ๋ค.
th:text="${question.subject}"
ํ ์คํธ๋ th:text ์์ฑ ๋์ ์ ๋ค์์ฒ๋ผ ๋๊ดํธ๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ ์ง์ ์ถ๋ ฅํ ์ ์๋ค.
<tr th:each="question : ${`questionList`}">
<td>[[${question.subject}]]</td>
<td>[[${question.createDate}]]</td>
</tr>
์ด ์ฑ ์์๋ ์๋ก์ด ํ์๋ฆฌํ ๋ฌธ๋ฒ์ด ๋์ฌ ๋๋ง๋ค ์์ธํ ์ค๋ช ํ ๊ฒ์ด๋ฏ๋ก ์ง๊ธ ๋น์ฅ ๋ชจ๋ ํ์๋ฆฌํ์ ์์ฑ์ ๋ํด ์์ ๋ ํ์๋ ์๋ค.
์ด์๊ณผ ๊ฐ์ด ์ง๋ฌธ ๋ชฉ๋ก์ ๋ง๋ค์๋ค.
์ถํํ๋ค!!