[Spring] thymeleaf, bootstrap์ ํตํด ๊ธฐ๋ณธ ์ํ ์ ์ฅ ์น ์ฌ์ดํธ ๋ง๋ค๊ธฐ
๊น์ํ ๋์ '์คํ๋ง MVC 1ํธ - ๋ฐฑ์๋ ์น ๊ฐ๋ฐ ํต์ฌ ๊ธฐ์ '์ ๋ณด๊ณ ์ ๋ฆฌํ ๊ธ์ ๋๋ค ๐
์คํ๋ง MVC 1ํธ - ๋ฐฑ์๋ ์น ๊ฐ๋ฐ ํต์ฌ ๊ธฐ์ - ์ธํ๋ฐ | ๊ฐ์
์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํ ๋ ํ์ํ ๋ชจ๋ ์น ๊ธฐ์ ์ ๊ธฐ์ด๋ถํฐ ์ดํดํ๊ณ , ์์ฑํ ์ ์์ต๋๋ค. ์คํ๋ง MVC์ ํต์ฌ ์๋ฆฌ์ ๊ตฌ์กฐ๋ฅผ ์ดํดํ๊ณ , ๋ ๊น์ด์๋ ๋ฐฑ์๋ ๊ฐ๋ฐ์๋ก ์ฑ์ฅํ ์ ์์ต๋๋ค., -
www.inflearn.com
- ์ง๋ ํฌ์คํ ๊ณผ ์ด์ด์ง๋๋ค :D
[Spring] HTTP ์์ฒญ ํ๋ผ๋ฏธํฐ, ์๋ต ๋ฐ์ดํฐ ์์ฑ, HttpMessageConverter์ ArgumentResolver
- ๊น์ํ ๋์ '์คํ๋ง MVC 1ํธ - ๋ฐฑ์๋ ์น ๊ฐ๋ฐ ํต์ฌ ๊ธฐ์ '์ ๋ณด๊ณ ์ ๋ฆฌํ ๊ธ์ ๋๋ค ๐ ์คํ๋ง MVC 1ํธ - ๋ฐฑ์๋ ์น ๊ฐ๋ฐ ํต์ฌ ๊ธฐ์ - ์ธํ๋ฐ | ๊ฐ์ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํ ๋ ํ์ํ ๋ชจ๋ ์น ๊ธฐ
cl8d.tistory.com
- ์ง๋ ํฌ์คํ ์์๋ HTTP ์์ฒญ/์๋ต ํ๋ผ๋ฏธํฐ์ ArgumentResolver, ๊ทธ๋ฆฌ๊ณ HttpMessageConverter์ ๋์ ๋ฐฉ์์ ์์๋ณด์๋ค.
- ์ด๋ฒ ํฌ์คํ ์์๋ ๋ณธ๊ฒฉ์ ์ผ๋ก ์์ ์น ์ฌ์ดํธ๋ฅผ ๋ง๋ค๋ฉด์ ์ง๊ธ๊น์ง ๋ฐฐ์ด ๋ด์ฉ์ ์์ฉํด๋ณด์.
- ์คํ๋ง MVC 1์ ๋ง์ง๋ง ํฌ์คํ ์ด ๋ ์์ ์ ๋๋ค :D
| ๊ฐ๋จํ ์น ํ์ด์ง ๋ง๋ค๊ธฐ
- dependency๋ก Spring Web, Thymeleaf, Lombok์ ์ถ๊ฐํด์ฃผ์.
- ๊ฐ๋จํ ์ํ ์ฃผ๋ฌธ ์น ์ฌ์ดํธ๋ฅผ ๋ง๋ค ์์ ์ด๋ค.
[SItem.java]
- ๋์ ๊ฒฝ์ฐ ์ง๊ธ๊น์ง ํฌ์คํ ํ๋ ๋ด์ฉ์ ํ๋ก์ ํธ๊ฐ ๋ค ํ๋์ ํ๋ก์ ํธ์ฌ์ ๋๋ฉ์ธ ์ด๋ฆ์ ์ ๊ฒน์น๊ฒ ํ๊ธฐ ์ํด ์ด๋ ๊ฒ ์ค์ ํ์๋ค.
@Getter
@NoArgsConstructor
public class SItem {
private Long id;
private String itemName;
private Integer price;
private Integer quantity;
public SItem(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
public void changeId(Long id) {
this.id = id;
}
public void changeItemInfo(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
- setter๋ฅผ ์ ์ด๊ธฐ ์ํด์ ์ต๋ํ ๋ฉ์๋ ๋จ์๋ก ์ด์๋ค.
[SItemRepository.java]
@Repository
public class SItemRepository {
private static final Map<Long, SItem> store = new ConcurrentHashMap<>();
private static AtomicLong sequence = new AtomicLong(0);
public SItem save(SItem item) {
item.changeId(sequence.incrementAndGet());
store.put(item.getId(), item);
return item;
}
public SItem findById(Long id) {
return store.get(id);
}
public List<SItem> findAll() {
return new ArrayList<>(store.values());
}
public void update(Long itemId, SItem updateInfo) {
SItem findItem = store.get(itemId);
findItem.changeItemInfo(updateInfo.getItemName(), updateInfo.getPrice(), updateInfo.getQuantity());
}
public void clearStore() {
store.clear();
}
}
- ๋์์ฑ ์ด์๋ก ์ธํด์ concurrentHashMap๊ณผ AtomicLong์ ์ฌ์ฉํ์๋ค. (์ฌ์ฉํ์ง ์์๋ ๋ฌด๋ฐฉํ๋ค)
[SItemRepositoryTest.java]
class SItemRepositoryTest {
SItemRepository repository = new SItemRepository();
@AfterEach
void afterEach() {
repository.clearStore();
}
@Test
public void save() throws Exception {
// given
SItem item = new SItem("itemA", 10000, 10);
// when
SItem savedItem = repository.save(item);
// then
SItem findItem = repository.findById(savedItem.getId());
assertThat(findItem).isEqualTo(savedItem);
}
@Test
public void findAll() throws Exception {
// given
SItem item1 = new SItem("itemA", 10000, 10);
SItem item2 = new SItem("itemB", 20000, 20);
repository.save(item1);
repository.save(item2);
// when
List<SItem> findItems = repository.findAll();
// then
assertThat(findItems.size()).isEqualTo(2);
assertThat(findItems).contains(item1, item2);
}
@Test
public void updateItem() throws Exception {
// given
SItem item1 = new SItem("itemA", 10000, 10);
SItem savedItem = repository.save(item1);
Long itemId = savedItem.getId();
// when
SItem updateItem = new SItem("itemB", 20000, 20);
repository.update(itemId, updateItem);
SItem findItem = repository.findById(itemId);
// then
assertThat(findItem.getItemName()).isEqualTo(updateItem.getItemName());
assertThat(findItem.getPrice()).isEqualTo(updateItem.getPrice());
assertThat(findItem.getQuantity()).isEqualTo(updateItem.getQuantity());
}
}
- ๊ฐ ๊ธฐ๋ฅ์ ๋ํ ๊ฐ๋จํ ํ ์คํธ ์ฝ๋์ด๋ค. ๋ชจ๋ ์ ๋์๊ฐ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
- ์ด์ , html์ ์ค์ ํ๊ธฐ ์ํด ๊ฐ๋จํ ๋ถํธ์คํธ๋ฉ์ ๋ค์ด๋ก๋ ๋ฐ์๋์.
Download
Download Bootstrap to get the compiled CSS and JavaScript, source code, or include it with your favorite package managers like npm, RubyGems, and more.
getbootstrap.com
- ์์ถ ํด์ ํ bootstrap.min.css๋ฅผ ๋ณต์ฌํด์ resources/static/css ๋ฐ์ ์ถ๊ฐํด์ฃผ์๋ค.
- ์ดํ ํ์ผ๋ค์ resources/static/basic ํด๋ ๋ฐ์๋ค ์ถ๊ฐํ์๋ค.
[items.html]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="../css/bootstrap.min.css" rel="stylesheet">
<title>item List</title>
</head>
<body>
<div class="container" style="max-width: 600px">
<div class="py-5 text-center">
<h2 class="font-monospace fw-bold">Item List</h2>
<button class="btn btn-warning float-end text-white"
onclick="location.href='addForm.html'"
type="button">
โ
</button>
</div>
<div class="mt-4 font-monospace">
<table class="table table-hover table-bordered">
<thead>
<tr class="text-center">
<th>ID</th>
<th>์ํ๋ช
</th>
<th>๊ฐ๊ฒฉ</th>
<th>์๋</th>
</tr>
</thead>
<tbody class="text-center">
<tr>
<td><a href="item.html">1</a></td>
<td><a href="item.html">ํ
์คํธ ์ํ1</a></td>
<td>10000</td>
<td>10</td>
</tr>
<tr>
<td><a href="item.html">2</a></td>
<td><a href="item.html">ํ
์คํธ ์ํ2</a></td>
<td>20000</td>
<td>20</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>
- ๋์ถฉ ์ด๋ฐ ๋๋์ผ๋ก ์์ดํ ๋ฆฌ์คํธ๋ฅผ ๋ํ๋ผ ์ ์๊ฒ ๋ง๋ค์๋ค.
[item.html]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="../css/bootstrap.min.css" rel="stylesheet">
<title>item Detail</title>
</head>
<body>
<div class="container">
<div class="py-5 text-center">
<h2 class="font-monospace fw-bold">Item Detail</h2>
</div>
<div class="row fw-bold font-monospace">
<div class="col text-center">
<label for="itemId">ID</label>
<input type="text" id="itemId" name="itemId"
class="form-control text-center"
value="1" readonly>
</div>
<div class="col text-center">
<label for="itemName">Name</label>
<input type="text" id="itemName" name="itemName"
class="form-control text-center"
value="์ํA" readonly>
</div>
</div>
<div class="row mt-2 fw-bold font-monospace">
<div class="col text-center">
<label for="price">Price</label>
<input type="text" id="price" name="price"
class="form-control text-center"
value="10000" readonly>
</div>
<div class="col text-center">
<label for="quantity">Quantity</label>
<input type="text" id="quantity" name="quantity"
class="form-control text-center"
value="10" readonly>
</div>
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-warning btn-lg"
onclick="location.href='editForm.html'" type="button">
โ๐ป
</button>
</div>
<div class="col">
<button class="w-100 btn btn-info btn-lg"
onclick="location.href='items.html'" type="button">
๐
</button>
</div>
</div>
</div>
</body>
</html>
- ๊ฐ item์ ๋ํ ์ธ๋ถ ์ ๋ณด๋ฅผ ๋ํ๋ด๋ ํ๋ฉด์ด๋ค.
- ์๋ ๋ ธ๋์ ๋ฒํผ์ ์์ , ํ๋์ ๋ฒํผ์ ๋ชฉ๋ก์ผ๋ก ๋์๊ฐ๋ ๋ฒํผ์ด๋ค.
[addForm.html]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="../css/bootstrap.min.css" rel="stylesheet">
<title>Item Save Form</title>
<style>
</style>
</head>
<body>
<div class="container">
<div class="py-5 text-center">
<h2 class="font-monospace fw-bold">Item Save Form</h2>
</div>
<form action="item.html" method="post">
<div class="row fw-bold font-monospace">
<div class="col text-center">
<label for="itemName">Name</label>
<input type="text" id="itemName" name="itemName"
class="form-control text-center"
placeholder="Please Enter an Item Name.">
</div>
</div>
<div class="row mt-2 fw-bold font-monospace">
<div class="col text-center">
<label for="price">Price</label>
<input type="text" id="price" name="price"
class="form-control text-center"
placeholder="Please Enter an Item Price.">
</div>
<div class="col text-center">
<label for="quantity">Quantity</label>
<input type="text" id="quantity" name="quantity"
class="form-control text-center"
placeholder="Please Enter an Item Quantity.">
</div>
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-warning btn-lg"
type="submit">
โญ
</button>
</div>
<div class="col">
<button class="w-100 btn btn-danger btn-lg"
onclick="location.href='items.html'" type="button">
โ
</button>
</div>
</div>
</form>
</div>
</body>
</html>
- ์์ดํ ์ ์ฅ form์ด๋ค. ์ํ ์ด๋ฆ, ๊ฐ๊ฒฉ, ์๋์ ๋ฑ๋กํ ์ ์๋ค.
[editForm.html]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="../css/bootstrap.min.css" rel="stylesheet">
<title>Item Edit Form</title>
<style>
</style>
</head>
<body>
<div class="container">
<div class="py-5 text-center">
<h2 class="font-monospace fw-bold">Item Edit Form</h2>
</div>
<form action="item.html" method="post">
<div class="row fw-bold font-monospace">
<div class="col text-center">
<label for="id">Id</label>
<input type="text" id="id" name="id"
class="form-control text-center"
value="1" readonly>
</div>
<div class="col text-center">
<label for="itemName">Name</label>
<input type="text" id="itemName" name="itemName"
class="form-control text-center"
value="์ํA">
</div>
</div>
<div class="row mt-2 fw-bold font-monospace">
<div class="col text-center">
<label for="price">Price</label>
<input type="text" id="price" name="price"
class="form-control text-center"
value="10000">
</div>
<div class="col text-center">
<label for="quantity">Quantity</label>
<input type="text" id="quantity" name="quantity"
class="form-control text-center"
value="10">
</div>
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-warning btn-lg"
type="submit">
โญ
</button>
</div>
<div class="col">
<button class="w-100 btn btn-danger btn-lg"
onclick="location.href='items.html'" type="button">
โ
</button>
</div>
</div>
</form>
</div>
</body>
</html>
- ์ํ ์์ ํผ๋ ๋์ผํ๊ฒ ๋ง๋ค์ด์ฃผ์๋ค.
| ์ปจํธ๋กค๋ฌ์ ๋ทฐ ํ ํ๋ฆฟ ๋ง๋ค๊ธฐ
- ๋์ ์ผ๋ก ๊ฐ์ ์์ ํ ์ ์๋๋ก ํ๊ธฐ ์ํด์. html ๊ฒฝ๋ก๋ฅผ resources/templates/basic ๋ฐ์ผ๋ก ์ฎ๊ฒจ์ฃผ์๋ค.
[SItemController.java]
@Controller
@RequestMapping("/basic/items")
@RequiredArgsConstructor
public class SItemController {
private final SItemRepository repository;
@GetMapping
public String items(Model model) {
List<SItem> items = repository.findAll();
model.addAttribute("items", items);
return "basic/items";
}
// ํ
์คํธ ๋ฐ์ดํฐ ์ถ๊ฐ (์์กด๊ด๊ณ ์ฃผ์
์ดํ ์คํ)
@PostConstruct
public void init() {
repository.save(new SItem("itemA", 10000, 10));
repository.save(new SItem("itemB", 20000, 20));
}
}
- DB๋ฅผ ์ฌ์ฉํ์ง ์๊ธฐ ๋๋ฌธ์ ์๋ฒ ์ฌ์์๋ง๋ค ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ ์ ์์ผ๋๊น @PostConstruct๋ฅผ ์ฌ์ฉํด์ ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ด์ฃผ์๋ค.
- ์ ์ฒด ์ํ ๋ชฉ๋ก์ ์กฐํํ ์ ์๋ ์ปจํธ๋กค๋ฌ ๋ฉ์๋๋ง ์์ฑํ์๋ค.
- ์ฐ๋ฆฌ๊ฐ ๋ง๋ html์ ํ์๋ฆฌํ๋ฅผ ์ ์ฉํด๋ณด์.
[items.html] - ์์
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link href="../css/bootstrap.min.css"
th:href="@{/css/bootstrap.min.css}"
rel="stylesheet">
<title>item List</title>
</head>
<body>
<div class="container" style="max-width: 600px">
<div class="py-5 text-center">
<h2 class="font-monospace fw-bold">Item List</h2>
<button class="btn btn-warning float-end text-white"
onclick="location.href='addForm.html'"
th:onclick="|location.href='@{/basic/items/add}'|"
type="button">
โ
</button>
</div>
<div class="mt-4 font-monospace">
<table class="table table-hover table-bordered">
<thead>
<tr class="text-center">
<th>ID</th>
<th>์ํ๋ช
</th>
<th>๊ฐ๊ฒฉ</th>
<th>์๋</th>
</tr>
</thead>
<tbody class="text-center">
<tr th:each="item : ${items}">
<td><a href="item.html"
th:href="@{/basic/items/{itemId}(itemId=${item.id})}"
th:text="${item.id}">Id</a></td>
<td><a href="item.html"
th:href="@{|/basic/items/${item.id}|}"
th:text="${item.itemName}">ItemName</a></td>
<td th:text="${item.price}">Price</td>
<td th:text="${item.quantity}">Quantity</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>
โ th:href
- ๊ธฐ์กด์ href ๊ฐ์ th:href ๊ฐ์ผ๋ก ๋ณ๊ฒฝํ๋ค. ๊ฐ์ด ์๋ค๋ฉด ์๋ก ์์ฑํ๋ค.
- HTML์ ๊ทธ๋๋ก ์ฌ์ฉํ ๋๋ href ์์ฑ์ด, ๋ทฐ ํ ํ๋ฆฟ์ ๊ฑฐ์น๋ฉด th:href ๊ฐ์ด ๋์ ์ผ๋ก ๋์ฒด๋๋ค.
- ๋๋ถ๋ถ์ HTML ์์ฑ์ ํ์๋ฆฌํ๋ฅผ ํตํด th:๋ก ๋ณ๊ฒฝ์ด ๊ฐ๋ฅํ๋ค.
- HTML์ ํ์ผ๋ก ์ด์์ ๋๋ th:๊ฐ ์์ด๋ ์น ๋ธ๋ผ์ฐ์ ๋ ์ ์ ์๊ธฐ ๋๋ฌธ์, HTML ํ์ผ ๋ณด๊ธฐ + ํ ํ๋ฆฟ ๊ธฐ๋ฅ์ ํจ๊ป ํ ์ ์๋ค.
โ URL ๋งํฌ ํํ @{...}
- URL ๋งํฌ๋ฅผ @{...}๋ก ํํํ ์ ์๋ค. ์๋ธ๋ฆฟ ์ปจํ ์คํธ๋ฅผ ์๋์ผ๋ก ํฌํจ์ํจ๋ค.
(์๋ ์ ๊ฒฝ๋ก ์ด๋ฆ์ ์ง์ ํ์ด์ผ ํ๋๋ฐ, ์ง๊ธ์ ์ ์ ์จ์ ํฌ๊ฒ ์ ๊ฒฝ์ฐ์ง ์์๋ ๋๋ค.)
th:href="@{/basic/items/{itemId}(itemId=${item.id})}"
- itemId๊ฐ item.id์ ๊ฐ์ผ๋ก ์นํ์ด ๋๊ณ , ๊ฒฐ๊ณผ์ ์ผ๋ก ๊ฒฝ๋ก ๋ณ์์ {itemId}์ ๋ค์ด๊ฐ๋ค.
- ์ถ๊ฐ์ ์ผ๋ก, ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ ์์ฑํ ์ ์๋ค.
th:href="@{/basic/items/{itemId}(itemId=${item.id}, query='test')}"
--> /basic/items/1?query=test
- ๋ฆฌํฐ๋ด ๋์ฒด ๋ฌธ๋ฒ์ ์ฌ์ฉํ๋ฉด ๋ ๊ฐ๋จํ๊ฒ ํํํ ์ ์๋ค.
th:href="@{|/basic/items/${item.id}|}"
โ ์์ฑ ๋ณ๊ฒฝ th:onclick
- ์ฌ๊ธฐ์, ๋ฆฌํฐ๋ด ๋์ฒด ๋ฌธ๋ฒ์ด ์ฌ์ฉ๋์๋ค.
th:onclick="|location.href='@{/basic/items/add}'|"
|...| : ํ์๋ฆฌํ์์ ๊ธฐ์กด์๋ ๋ฌธ์, ํํ์์ด ๋ถ๋ฆฌ๋์ด ์์ด์ +๋ฅผ ํตํด ๋ํด์ ์ฌ์ฉํ์ด์ผ ํ๋๋ฐ, ||๋ฅผ ์ฌ์ฉํ๋ฉด ๊ทธ๋ด ํ์๊ฐ ์๋ค.
ex) th:text="'hi' + ${item.name} + '!'" -> th:text="|hi ${item.name}!|"
- ์ฐ๋ฆฌ๋ ๊ธฐ์กด์ด๋ผ๋ฉด ๊ฒฝ๋ก์ ๋ํด์ / ๋ผ๋ ๋ฌธ์๋ฅผ ๋ํด์คฌ์ด์ผ ํ๋๋ฐ, ๋ฆฌํฐ๋ด ๋์ฒด ๋ฌธ๋ฒ์ผ๋ก ํธํ๊ฒ ์ง์ ํด์ฃผ์๋ค.
โ ๋ฐ๋ณต ์ถ๋ ฅ th:each
th:each="item : ${items}
- 'items'๋ผ๋ ์ปฌ๋ ์ ์ ์๋ ๋ฐ์ดํฐ๋ฅผ ํ๋์ฉ ๊บผ๋ด์ item์ ๋ด์์ค๋ค. item.name ์ด๋ฐ ์์ผ๋ก ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค.
โ ๋ณ์ ํํ์ ${...}, ๋ด์ฉ ๋ณ๊ฒฝ th:text
th:text="${item.itemName}">ItemName</a></td>
- ๋ชจ๋ธ์ ํฌํจ๋ ๊ฐ์ด๋, ํ์๋ฆฌํ ๋ณ์๋ก ์ ์ธํ ๊ฐ์ ${...}์ ํตํด ์กฐํํ ์ ์๋ค.
- ๋ด์ฉ์ ๊ฐ์ th:text๋ฅผ ํตํด ๋ณ๊ฒฝ์ด ๊ฐ๋ฅํ๋ค.
- ์, ์ด์ ์ด์ด์ ๋๋จธ์ง ๋ถ๋ถ๋ ๋ง๋ค์ด๋ณด์.
[SItemController.java] - ์ถ๊ฐ
@GetMapping("/{itemId}")
public String item(@PathVariable Long itemId, Model model) {
SItem item = repository.findById(itemId);
model.addAttribute("item", item);
return "basic/item";
}
@GetMapping("/add")
public String addForm() {
return "basic/addForm";
}
- ์ํ ์์ธ์ ์ํ ๋ฑ๋ก์ ๊ดํ ํผ์ ์ถ๊ฐํ์๋ค.
[item.html] - ์์
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link href="../css/bootstrap.min.css"
th:href="@{/css/bootstrap.min.css}"
rel="stylesheet">
<title>item Detail</title>
</head>
<body>
<div class="container">
<div class="py-5 text-center">
<h2 class="font-monospace fw-bold">Item Detail</h2>
</div>
<div class="row fw-bold font-monospace">
<div class="col text-center">
<label for="itemId">ID</label>
<input type="text" id="itemId" name="itemId"
class="form-control text-center"
value="1" th:value="${item.id}" readonly>
</div>
<div class="col text-center">
<label for="itemName">Name</label>
<input type="text" id="itemName" name="itemName"
class="form-control text-center"
value="์ํA" th:value="${item.itemName}" readonly>
</div>
</div>
<div class="row mt-2 fw-bold font-monospace">
<div class="col text-center">
<label for="price">Price</label>
<input type="text" id="price" name="price"
class="form-control text-center"
value="10000" th:value="${item.price}" readonly>
</div>
<div class="col text-center">
<label for="quantity">Quantity</label>
<input type="text" id="quantity" name="quantity"
class="form-control text-center"
value="10" th:value="${item.quantity}" readonly>
</div>
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-warning btn-lg"
onclick="location.href='editForm.html'"
th:onclick="|location.href='@{/basic/items/{itemId}/edit(itemId=${item.id})}'|"
type="button">
โ๐ป
</button>
</div>
<div class="col">
<button class="w-100 btn btn-info btn-lg"
onclick="location.href='items.html'"
th:onclick="|location.href='@{/basic/items}'|"
type="button">
๐
</button>
</div>
</div>
</div>
</body>
</html>
โ ์์ฑ ๋ณ๊ฒฝ th:value
value="10" th:value="${item.quantity}"
- th:value๋ฅผ ํตํด item์ ์๋ quantity ์ ๋ณด๋ฅผ value์ ๋ด์์ค๋ค. (๋ณ์ ํํ์ ์ฌ์ฉ)
[addForm.html]
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link href="../css/bootstrap.min.css"
th:href="@{/css/bootstrap.min.css}"
rel="stylesheet">
<title>Item Save Form</title>
<style>
</style>
</head>
<body>
<div class="container">
<div class="py-5 text-center">
<h2 class="font-monospace fw-bold">Item Save Form</h2>
</div>
<form action="item.html" th:action method="post">
<div class="row fw-bold font-monospace">
<div class="col text-center">
<label for="itemName">Name</label>
<input type="text" id="itemName" name="itemName"
class="form-control text-center"
placeholder="Please Enter an Item Name.">
</div>
</div>
<div class="row mt-2 fw-bold font-monospace">
<div class="col text-center">
<label for="price">Price</label>
<input type="text" id="price" name="price"
class="form-control text-center"
placeholder="Please Enter an Item Price.">
</div>
<div class="col text-center">
<label for="quantity">Quantity</label>
<input type="text" id="quantity" name="quantity"
class="form-control text-center"
placeholder="Please Enter an Item Quantity.">
</div>
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-warning btn-lg"
type="submit">
โญ
</button>
</div>
<div class="col">
<button class="w-100 btn btn-danger btn-lg"
onclick="location.href='items.html'"
th:onclick="|location.href='@{/basic/items}'|"
type="button">
โ
</button>
</div>
</div>
</form>
</div>
</body>
</html>
โ ์์ฑ ๋ณ๊ฒฝ th:action
- HTML form์์ action์ ๊ฐ์ด ์์ผ๋ฉด ํ์ฌ URL์ ๋ฐ์ดํฐ๋ฅผ ์ ์กํด์ค๋ค.
- ์ฆ, ์ฌ๊ธฐ์ ํ์ฌ url์ด /basic/item/add์ด๊ธฐ ๋๋ฌธ์ ์ด URL๋ก POST ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๊ฒ ๋๋ค.
- ์ฐ๋ฆฌ๋ ์ํ ๋ฑ๋ก ํผ์ /basic/item/add์์ GET์ผ๋ก, ์ค์ ๋ฑ๋ก์ /basic/item/add์ POST ์ฒ๋ฆฌ๋ฅผ ํ์๋ค.
- ๊ฐ์ URL์ด์ง๋ง HTTP ๋ฉ์๋๋ง ๋ค๋ฅด๊ฒ ํ ๊ฒ์ด๋ค. (ํ๋์ ํผ์์ ๋ฑ๋ก ์ฒ๋ฆฌ๊น์ง ์งํ)
| ์ํ ๋ฑ๋ก/์์ ์งํํ๊ธฐ
- ์ํ ๋ฑ๋ก์ ๋ฉ์์ง ๋ฐ๋์ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ ํ์์ผ๋ก ์ ๋ฌํ๋ ๋ฐฉ์์ผ๋ก ์งํํ๋ค.
- content-type: application/x-www-form-urlencoded
[SItemController.java] - ์ถ๊ฐ
@PostMapping("/add")
public String addItemV1 (@RequestParam String itemName,
@RequestParam Integer price,
@RequestParam Integer quantity,
Model model) {
SItem item = new SItem(itemName, price, quantity);
repository.save(item);
model.addAttribute("item", item);
return "basic/item";
}
์ด ๋ฐฉ๋ฒ์ ์ ๋์ํ์ง๋ง... ์ด๋ ๊ฒ ๋ชจ๋ ํ๋ผ๋ฏธํฐ๋ฅผ @RequestParam์ ํตํด ๋ฐ๋ ๊ฑด ๋ถํธํ๋ค.
@PostMapping("/add")
public String addItemV2 (@ModelAttribute SItem item, Model model) {
repository.save(item);
model.addAttribute("item", item);
return "basic/item";
}
๊ฐ๋จํ๊ฒ ์ค์ธ ๋ฒ์ ์ด๋ค.
@ModelAttribute์ ๊ฒฝ์ฐ SItem ๊ฐ์ฒด ์์ฑ ๋ฐ ์์ฒญ ํ๋ผ๋ฏธํฐ์ ๊ฐ์ ํ๋กํผํฐ ์ ๊ทผ๋ฒ(setter ์ด์ฉ)์ผ๋ก ์ ๋ ฅํด์ค๋ค.
์ด ๊ฒฝ์ฐ์๋ SItem์ @Setter๊ฐ ์์ด์ผ ํ๋ค!! (ํน์ setXXX๋ผ๋ ์ด๋ฆ์ ๊ฐ์ง ๋ฉ์๋๋ฅผ ๋ง๋ค์ด์ฃผ์)
โญ ๊ฐ์ฒด๋ฅผ ์์ฑํ ๋ ๊ธฐ๋ณธ ์์ฑ์๊ฐ ์๋ ๋ค๋ฅธ ์์ฑ์๊ฐ ์์ผ๋ฉด ๊ทธ ์์ฑ์๋ก ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ์, ๊ธฐ๋ณธ ์์ฑ์๋ง ์์ผ๋ฉด setter๋ฅผ ํตํด ๋ฐ์ธ๋ฉ์ ์๋ํ๋ค.
๋ํ, model์๋ค๊ฐ @ModelAttribute๊ฐ ์ง์ ํ ๊ฐ์ฒด๋ฅผ ๋ฃ์ด์ฃผ๋ ์ญํ ๋ ํ๋ค!
model์ ๋ฃ์ ๋ ์ด๋ฆ์ @ModelAttribute์ ์ง์ ํ name(value) ์์ฑ์ ์ฌ์ฉํ๋ค.
@PostMapping("/add")
public String addItemV3 (SItem item) {
repository.save(sItem);
return "basic/item";
}
- ๋ ๊ฐ๋จํ๊ฒ ์ค์ธ ๋ฒ์ ์ด๋ค.
- @ModelAttribute๋ฅผ ์๋ตํ๊ณ , model.addAttribute() ๋ถ๋ถ์ ์ ๊ฑฐํ์๋ค.
- ์๋ตํ์ด๋ SItem ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ , ์์ฒญ๋ ํ๋ผ๋ฏธํฐ์ ๊ฐ(itemName, price, quantity)๋ฅผ setXXX ๋ฉ์๋๋ฅผ ํตํด ์ฃผ์ ํ๋ค.
- โญ ๋ชจ๋ธ์ ์ ์ฅ๋ ๋ 'ํด๋์ค๋ช '์ ์ฌ์ฉํ๋ค. ์ด๋, ํด๋์ค์ ์ฒซ๊ธ์๋ง ์๋ฌธ์๋ก ๋ณ๊ฒฝํ๋ค.
- ๊ทธ๋์, ์ ์ฝ๋์ ๊ฒฝ์ฐ model.addAttribute('sItem', item')์ด ๋๋ค๊ณ ๋ณผ ์ ์๋ค.
- ๊ทธ๋ ๊ธฐ ๋๋ฌธ์, ์ด ์ฝ๋๊ฐ ๋์ํ๋ ค๋ฉด html์์ item ๋ถ๋ถ์ sItem์ผ๋ก ๋ฐ๊พธ์ด ์ค์ผ ํ๋ค.
- ๋ง์ฐฌ๊ฐ์ง๋ก ์์ ๋ ๋ฐ๊พธ์ด์ฃผ์.
[SItemController.java] - ์ถ๊ฐ
@GetMapping("/{itemId}/edit")
public String editForm(@PathVariable Long itemId, Model model) {
SItem item = repository.findById(itemId);
model.addAttribute("item", item);
return "basic/editForm";
}
@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, SItem item, Model model) {
repository.update(itemId, item);
model.addAttribute("item", item);
return "redirect:/basic/items/{itemId}"
}
- ์์ ํผ๊ณผ ์ค์ ์์ ์ ์ฒ๋ฆฌํ๋ edit ๊ธฐ๋ฅ์ด๋ค.
- ์์ ํ์๋ ์ํ ์์ธ๋ฅผ ๋ณผ ์ ์๋๋ก ๋ฆฌ๋ค์ด๋ ํธ๋ฅผ ์งํํ์๋ค.
[editForm.html]
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link href="../css/bootstrap.min.css"
th:href="@{/css/bootstrap.min.css}"
rel="stylesheet">
<title>Item Edit Form</title>
<style>
</style>
</head>
<body>
<div class="container">
<div class="py-5 text-center">
<h2 class="font-monospace fw-bold">Item Edit Form</h2>
</div>
<form action="item.html" th:action method="post">
<div class="row fw-bold font-monospace">
<div class="col text-center">
<label for="id">Id</label>
<input type="text" id="id" name="id"
class="form-control text-center"
value="1" th:value="${item.id}" readonly>
</div>
<div class="col text-center">
<label for="itemName">Name</label>
<input type="text" id="itemName" name="itemName"
class="form-control text-center"
value="์ํA" th:value="${item.itemName}">
</div>
</div>
<div class="row mt-2 fw-bold font-monospace">
<div class="col text-center">
<label for="price">Price</label>
<input type="text" id="price" name="price"
class="form-control text-center"
value="10000" th:value="${item.price}">
</div>
<div class="col text-center">
<label for="quantity">Quantity</label>
<input type="text" id="quantity" name="quantity"
class="form-control text-center"
value="10" th:value="${item.quantity}">
</div>
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-warning btn-lg"
type="submit">
โญ
</button>
</div>
<div class="col">
<button class="w-100 btn btn-danger btn-lg"
onclick="location.href='items.html'"
th:onclick="|location.href='@{/basic/items/{itemId}(itemId=${item.id})}'|"
type="button">
โ
</button>
</div>
</div>
</form>
</div>
</body>
</html>
- ์ ๋ง ์ ๋์ํ๋ ๊ฑธ ๋ณผ ์ ์๋ค!
| RPG, Redirect
- ํ์ฌ ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์ฌ์ดํธ๋ ์ํ ๋ฑ๋ก ํ ์๋ก๊ณ ์นจ์ ํ๋ฉด ์ํ์ด ๊ณ์ ์ค๋ณตํด์ ์์ธ๋ค.
- ์๋ก๊ณ ์นจ : ๋ง์ง๋ง์ผ๋ก ์๋ฒ์ ์ ์กํ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ ์ก
- ์ํ ๋ฑ๋ก ํผ์ POST /add๊ฐ ๊ณ์ ์ฌ์ ์ก๋๊ธฐ ๋๋ฌธ์ ๋์ผํ ์ํ์ด ๊ณ์ ์ถ๊ฐ๋๋ ๊ฒ.
-> ์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด์, ์ํ ์ ์ฅ ํ์ ๋ทฐ ํ ํ๋ฆฟ์ด ์๋ ์ํ ์์ธ ํ๋ฉด์ผ๋ก redirect๋ฅผ ์์ผ์ฃผ์.
--> ์ด๋ฌ๋ฉด ์๋ก๊ณ ์นจ์ ํด๋ ์ํ ์์ธ ํ๋ฉด์ ๊ณ์ ๋จธ๋ฌผ๊ฒ ๋๋ค.
[SItemController.java] - ์์
@PostMapping("/add")
public String addItemV2 (SItem item, Model model) {
repository.save(item);
model.addAttribute("item", item);
return "redirect:/basic/items/" + item.getId();
}
- ์ด๋ฐ ๋ฌธ์ ํด๊ฒฐ ๋ฐฉ๋ฒ์ด RPG - POST/REDIRECT/GET ๋ฐฉ์์ด๋ค.
- ๊ทธ๋ฌ๋, ์์ฒ๋ผ redirect๋ฌธ์ ์์ฑํ๋ ๊ฑด URL ์ธ์ฝ๋ฉ์ด ์ ๋๊ธฐ ๋๋ฌธ์ ์ข์ง ์๋ค. ๋ค์์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์.
[SItemController.java] - ์์
@PostMapping("/add")
public String addItemV2 (SItem item, Model model, RedirectAttributes redirectAttributes) {
SItem savedItem = repository.save(item);
model.addAttribute("item", item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/basic/items/{itemId};
}
- ์ํ ์ ์ฅ ํ ์ ์ ์ฅ๋์๋ค๋ ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํด์ฃผ๊ธฐ ์ํด์ status๋ผ๋ ๋ณ์๋ฅผ ์ถ๊ฐํด์ฃผ์๋ค.
- ๋ํ +๋ฅผ ํตํ redirect๋ฌธ ๋์ , addAttribute๋ก ์ถ๊ฐํด์ค itemId๋ฅผ ์ฌ์ฉํ์๋ค. ( {itemId} )
- redirectAttributes๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฒฝ๋ก ๋ณ์ + ๋๋จธ์ง๋ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก ์ฒ๋ฆฌํด์ค๋ค.
์ URL) basic/items/3?status=true
[item.html]
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
...
</head>
<body>
<div class="container">
<div class="py-5 text-center">
<h2 class="font-monospace fw-bold">Item Detail</h2>
</div>
<h2 class="font-monospace fw-bold text-center text-danger"
th:if="${param.status}" th:text="'Save Success!'"></h2>
...
</body>
</html>
โ th:if
- ํ๊ทธ์ ๋ํ ์กฐ๊ฑด์ ๊ฑธ ์ ์๋ค.
- ${param.status} -> ํ์๋ฆฌํ์์ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ฅผ ์กฐํํ ๋ ์ฌ์ฉํ ์ ์๋ ๊ธฐ๋ฅ์ด๋ค! param.~์ผ๋ก ์กฐํํ๋ฉด ๋๋ค.
- ์์์ง๋ ์์ง๋ง ์ ๋ฌ๋ค.
- ์ด๋ ๊ฒ ์คํ๋ง MVC1 ๊ฐ์๋ฅผ ์๊ฐํ์๋ค. ์ฝ๋ ๋ ๋ฒจ์์ ๊น๋ณด๋ฉด์ ๊ณต๋ถํ๋ ๊ฒ ๋ณต์ตํ ๋ ์ ์ผ ์ข์๋ ๊ฒ ๊ฐ๋ค.
- ๋นจ๋ฆฌ ์คํ๋ง MVC2๋ ๋ณต์ตํด์ผ๊ฒ ๋ค... ์์์์...!