
/write ํผ์์ title, price ์
๋ ฅ โ /add๋ก POSTItemRepository.save)@ModelAttribute)๊ณผ ๊ณตํต UI ์ฌ์ฌ์ฉ์ ์ ์ง๋ณด์ ํต์ฌ| ํค์๋ | ์ค๋ช |
|---|---|
@ModelAttribute | ์์ฒญ ํ๋ผ๋ฏธํฐ๋ฅผ ์๋ฐ ๊ฐ์ฒด์ ์๋ ๋ฐ์ธ๋ฉ |
@PostMapping | POST ์์ฒญ ์ฒ๋ฆฌ ๋ฉ์๋ ๋งคํ |
ItemRepository.save() | JPA๋ก ํ ์ถ๊ฐ/์์ |
th:fragment | ์ฌ์ฌ์ฉํ HTML ๋ธ๋ก ์ ์ |
th:replace | ์ ์๋ fragment๋ฅผ ์นํํด ํฌํจ |
templates vs static | ํ
ํ๋ฆฟ(๋์ )์ templates์์ return "๋ทฐ๋ช
" / ์ ์ ์ static์์ URL ์ง์ ์ ๊ทผ |
๐ src/main/java/com/apple/shop/item/Item.java
package com.apple.shop.item;
import jakarta.persistence.*;
@Entity
public class Item {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private Integer price;
// getter/setter
public Long getId() { return id; }
public String getTitle() { return title; }
public Integer getPrice() { return price; }
public void setTitle(String title) { this.title = title; }
public void setPrice(Integer price) { this.price = price; }
}
@Entity : JPA๊ฐ ํ
์ด๋ธ๊ณผ ๋งคํ @Id @GeneratedValue : ๊ธฐ๋ณธํค ์๋์์ฑ title, price๋ ์ปฌ๋ผ์ผ๋ก ๋งคํ๋จ(๊ธฐ๋ณธ ์ ๋ต)๐ src/main/java/com/apple/shop/item/ItemRepository.java
package com.apple.shop.item;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ItemRepository extends JpaRepository<Item, Long> {
}
JpaRepository๋ง ์์ํด๋ save, findAll ๋ฑ ๊ธฐ๋ณธ CRUD ์ ๊ณต๐ src/main/java/com/apple/shop/BasicController.java
package com.apple.shop;
import com.apple.shop.item.Item;
import com.apple.shop.item.ItemRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
public class BasicController {
@Autowired
private ItemRepository itemRepository;
// ์์ฑ ํผ
@GetMapping("/write")
public String write() {
return "write"; // templates/write.html
}
// (A) ์๋ ๋ฐ์ธ๋ฉ ์์
@PostMapping("/add-manual")
public String addManual(@RequestParam String title,
@RequestParam Integer price) {
if (title == null || title.isBlank() || price == null || price < 0) {
return "redirect:/write";
}
Item item = new Item();
item.setTitle(title);
item.setPrice(price);
itemRepository.save(item);
return "redirect:/list";
}
// (B) @ModelAttribute ์๋ ๋ฐ์ธ๋ฉ
@PostMapping("/add")
public String add(@ModelAttribute Item item) {
if (item.getTitle() == null || item.getTitle().isBlank()
|| item.getPrice() == null || item.getPrice() < 0) {
return "redirect:/write";
}
itemRepository.save(item);
return "redirect:/list";
}
// ๋ชฉ๋ก ํ์ด์ง(์ํ)
@GetMapping("/list")
public String list() {
return "list"; // templates/list.html
}
}
return "write": templates/ ๊ธฐ์ค์ผ๋ก write.html ๋ ๋ @RequestParam์ผ๋ก ์ง์ ๊ฐ ๊บผ๋ด์ Item ์ฑ์ฐ๊ธฐ @ModelAttribute Item item์ผ๋ก ์๋ ๋ฐ์ธ๋ฉ (ํผ name โโ ํ๋๋ช
๋งค์นญ)๐ src/main/resources/templates/nav.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Nav</title>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;800&display=swap" rel="stylesheet">
<style>
.nav { display: flex; padding: 10px; align-items: center; font-family: 'Montserrat', sans-serif; }
.nav a { margin-right: 10px; text-decoration: none; font-weight: 400; letter-spacing: -0.5px; }
.nav .logo { font-weight: 800; }
input, button { padding: 8px 13px; margin-top: 5px; border: 1px solid grey; border-radius: 4px; vertical-align: middle; }
button { background: black; color: white; border: none; }
input { display: block; }
</style>
</head>
<body>
<div class="nav" th:fragment="navbar">
<a class="logo" th:href="@{/}">SpringMall</a>
<a th:href="@{/list}">List</a>
<a th:href="@{/write}">Write</a>
</div>
</body>
</html>
th:fragment="navbar": ์ฌ์ฌ์ฉ ๋ธ๋ก ์ ์ ๐ src/main/resources/templates/write.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>
<form th:action="@{/add}" method="post">
<input name="title" placeholder="์ํ๋ช
">
<input name="price" placeholder="๊ฐ๊ฒฉ" type="number" min="0">
<button type="submit">๋ฑ๋ก</button>
</form>
</body>
</html>
th:replace="~{nav.html :: navbar}": div ์ ์ฒด๋ฅผ Navbar๋ก ์นํ th:action="@{/add}": ์ปจํ
์คํธ ๊ฒฝ๋ก ๊ณ ๋ คํ ์์ ํ URL ๋ฐ์ธ๋ฉ์ฐธ๊ณ
th:insert๋ ์์ ์ฝ์ ,th:replace๋ ์นํ(๋ณดํต replace ์ฌ์ฉ)- fragment๋ ์ฌ๋ฌ ํ์ผ์์ ๋ฐ๋ณต ์ฌ์ฉ OK
nav.html :: navbar('๋ฐ์ดํฐ1')์ฒ๋ผ ์ธ์ ์ ๋ฌ๋ ๊ฐ๋ฅ(ํ์ ์ ๊ฒ์)
| ๋ฌธ๋ฒ/๋ฉ์๋ | ์ค๋ช |
|---|---|
@ModelAttribute | ํ๋ผ๋ฏธํฐ โ ๊ฐ์ฒด ์๋ ๋งคํ |
@PostMapping("/add") | ํผ POST ์ฒ๋ฆฌ |
Repository.save() | JPA ์ ์ฅ(Insert/Update) |
th:fragment | ๊ณตํต UI ๋ธ๋ก ์ ์ |
th:replace | ๋ค๋ฅธ ํ ํ๋ฆฟ์์ fragment ์นํ |
templates/*.html | return "๋ทฐ๋ช
"์ผ๋ก ๋ ๋๋ง ๋์ |
@ModelAttribute๋ก ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ ๊ฑฐ โ ๊ฐ๋
์ฑโ @ModelAttribute๋ก ํผ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ฒด๋ก ์๋ ๋ฐ์ธ๋ฉ itemRepository.save(item)๋ก DB ์ ์ฅ ํ ๋ฆฌ๋ค์ด๋ ํธ th:fragment + th:replace๋ก Navbar ์ฌ์ฌ์ฉ