๐ ์ค๋ ๋ฐฐ์ด ํต์ฌ 3์ค ์์ฝ
@GetMapping("/detail/{id}") + @PathVariable๋ก ํ๋์ API๋ก ์ฌ๋ฌ ์ํ ์์ธ ์ฒ๋ฆฌ
itemRepository.findById(id)๋ Optional์ ๋ฐํํ๋ฏ๋ก ๋ถ์ฌ ์ผ์ด์ค๋ฅผ ์์ ํ๊ฒ ์ฒ๋ฆฌ
Model๋ก ์กฐํ ๊ฒฐ๊ณผ๋ฅผ ํ
ํ๋ฆฟ์ ๋ฐ์ธ๋ฉํด detail.html ๋ ๋๋ง
โ
์ด๋ค ๊ธฐ๋ฅ์ ๋ง๋๋ ๊ฒ์ธ๊ฐ?
/detail/{id} ์์ฒญ์ ๋ํด ํด๋น ์ํ์ ์์ธ๋ฅผ ๋ณด์ฌ์ฃผ๊ธฐ
- URL์
{id} ๊ฐ์ ๋ฐ์ DB์์ ๋จ๊ฑด ์กฐํ
- ์กฐํ ์คํจ ์ 404/๋ฆฌ๋ค์ด๋ ํธ ๋ฑ ์์ ์ฒ๋ฆฌ
๐ ์ ์ด๊ฑธ ๋ฐฐ์์ผ ํ์ง?
- ๋ชฉ๋ก โ ์์ธ(1๊ฑด) ํจํด์ ๋ชจ๋ ์๋น์ค์ ๊ธฐ๋ณธ
- URL ํ๋ผ๋ฏธํฐ, Optional ์์ ์ฒ๋ฆฌ ํจํด์ ์ค๋ฌด ๋น์ถ
- ์ดํ ์ฅ๋ฐ๊ตฌ๋, ์ฃผ๋ฌธ, ์์ /์ญ์ ๋ก ์์ฐ์ค๋ฌ์ด ํ์ฅ
๐ ๊ฐ๋
์ ๋ฆฌ
| ๊ฐ๋
| ์ค๋ช
|
|---|
| URL ํ๋ผ๋ฏธํฐ | /detail/{id}์ฒ๋ผ ๊ฒฝ๋ก ์ผ๋ถ๋ฅผ ๋ณ์๋ก ๋ฐ๋ ๋ฐฉ์ |
@PathVariable | ๊ฒฝ๋ก ๋ณ์ ๊ฐ์ ๋ฉ์๋ ์ธ์๋ก ๋ฐ์ธ๋ฉ |
Optional<T> | ๊ฐ์ด ์์ ์ ์์์ ํํํ๋ ์ปจํ
์ด๋ |
findById(id) | Optional<Entity> ๋ฐํ (JPA ๊ธฐ๋ณธ) |
Model | ์ปจํธ๋กค๋ฌ โ ๋ทฐ๋ก ๋ฐ์ดํฐ ์ ๋ฌ |
templates/*.html | return "๋ทฐ๋ช
"์ผ๋ก Thymeleaf ๋ ๋๋ง |
โ๏ธ ๊ตฌํ ํ๋ฆ ๋ฐ ์ฝ๋
๐ src/main/java/com/apple/shop/ItemController.java
package com.apple.shop;
import com.apple.shop.item.Item;
import com.apple.shop.item.ItemRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
public class ItemController {
private final ItemRepository itemRepository;
public ItemController(ItemRepository itemRepository) {
this.itemRepository = itemRepository;
}
@GetMapping("/detail/{id}")
public String detail(@PathVariable Long id, Model model) {
var item = itemRepository.findById(id)
.orElse(null);
if (item == null) {
return "redirect:/list";
}
model.addAttribute("item", item);
return "detail";
}
}
{id} ์๋ฆฌ์ ์ค๋ ์ซ์๋ฅผ @PathVariable Long id๋ก ๋ฐ๋๋ค
findById(id) ๊ฒฐ๊ณผ๋ Optional<Item> โ ๋ถ์ฌ ์ผ์ด์ค ์ฒ๋ฆฌ
- ๋ชจ๋ธ์ ๋ด์
detail.html์์ ${item.*}๋ก ์ ๊ทผ
๐ src/main/resources/templates/detail.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="${item.title}">์์ธํ์ด์ง</title>
<style>
.detail { text-align: center; }
.detail img { max-width: 30%; display: block; margin: auto; }
</style>
</head>
<body>
<div th:replace="~{nav.html :: navbar}"></div>
<div class="detail">
<h4>์์ธํ์ด์ง</h4>
<img src="https://placehold.co/300" alt="thumbnail">
<h4 th:text="${item.title}">์ํ๋ช
</h4>
<p th:text="${#numbers.formatInteger(item.price, 0, 'COMMA')}">๊ฐ๊ฒฉ</p>
</div>
</body>
</html>
${item.title}, ${item.price}๋ก ๋ฐ์ธ๋ฉ๋ ๋ฐ์ดํฐ ์ถ๋ ฅ
th:replace๋ก ๊ณตํต Navbar ์ฌ์ฌ์ฉ
๐ src/main/resources/templates/list.html (์์ธ ๋งํฌ ์์)
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>๋ชฉ๋ก</title></head>
<body>
<div th:replace="~{nav.html :: navbar}"></div>
<h3>์ํ ๋ชฉ๋ก</h3>
<ul>
<li th:each="it : ${items}">
<a th:href="@{|/detail/${it.id}|}" th:text="${it.title}">์ํ๋ช
</a>
<span th:text="${it.price}">๊ฐ๊ฒฉ</span>
</li>
</ul>
</body>
</html>
- ๊ฐ ํญ๋ชฉ์
/detail/{id} ๋งํฌ ์ฐ๊ฒฐ
๐ ์ฌ์ฉ๋ ๊ฐ๋
์์ฝ
| ํค์๋ | ํ ์ค ์์ฝ |
|---|
@GetMapping("/detail/{id}") | ์์ธํ์ด์ง ๋ผ์ฐํ
|
@PathVariable | URL ๊ฒฝ๋ก ๊ฐ์ ์ธ์๋ก ์์ |
Optional | ๊ฐ ๋ถ์ฌ๋ฅผ ํํ, NPE ๋ฐฉ์ง |
findById | ๋จ๊ฑด ์กฐํ ๋ฉ์๋ |
Model | ํ
ํ๋ฆฟ ๋ฐ์ดํฐ ์ ๋ฌ |
๐ก ์ด๋ฐ ๊ณณ์ ํ์ฉํ ์ ์์ด์
- ์ํ/๊ฒ์๊ธ/ํ๋กํ ์์ธ
- ๊ณต์ง/์ฃผ๋ฌธ/๋๊ธ ๋จ๊ฑด ์กฐํ
- ๋ชฉ๋ก โ ์์ธ โ ์์ /์ญ์ ํ์ค ํ๋ฆ
โ๏ธ ๊ฐ์ธ ์ ๋ฆฌ ๋ฐ ํ๊ณ
- ํ๋์ API๋ก N๊ฐ์ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋ ค๋ฉด URL ํ๋ผ๋ฏธํฐ๊ฐ ํ์
- Optional๋ก โ์์โ์ ๋ช
์์ ์ผ๋ก ๋ค๋ฃจ๋ฉด ์์ ์ฑ์ด ์ฌ๋ผ๊ฐ๋ค
- ํ
ํ๋ฆฟ ๋ฐ์ธ๋ฉ์ผ๋ก UI/๋ฐ์ดํฐ ๋ถ๋ฆฌ๊ฐ ์์ฐ์ค๋ฌ์์ง๋ค