# Spring MVC - ์›น ํŽ˜์ด์ง€ ๋งŒ๋“ค๊ธฐ with Thymeleaf
Study Repository

Spring MVC - ์›น ํŽ˜์ด์ง€ ๋งŒ๋“ค๊ธฐ with Thymeleaf

by rlaehddnd0422

์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ์ด์ „ ํฌ์ŠคํŒ…์—์„œ ๊ณต๋ถ€ํ•œ MVC์˜ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ๊ณผ ๋ทฐ ํ…œํ”Œ๋ฆฟ Thymeleaf์„ ์ด์šฉํ•ด ๊ฐ„๋‹จํ•˜๊ฒŒ ์ƒํ’ˆ์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์š”๊ตฌ์‚ฌํ•ญ

์ƒํ’ˆ ๋„๋ฉ”์ธ ๋ชจ๋ธ

  • ์ƒํ’ˆ ID
  • ์ƒํ’ˆ๋ช…
  • ๊ฐ€๊ฒฉ

์ƒํ’ˆ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ

  • ์ƒํ’ˆ ๋ชฉ๋ก
  • ์ƒํ’ˆ ์ƒ์„ธ
  • ์ƒํ’ˆ ๋“ฑ๋ก
  • ์ƒํ’ˆ ์ˆ˜์ •

์ƒํ’ˆ ๋„๋ฉ”์ธ ๊ฐœ๋ฐœ

Item - ์ƒํ’ˆ ๊ฐ์ฒด

@Getter @Setter
public class Item {

    private Long id;
    private String itemName;
    private Integer price;
    private Integer quantity;

    public Item(){}

    public Item(String itemName, Integer price, Integer quantity) {
        this.itemName = itemName;
        this.price = price;
        this.quantity = quantity;
    }
}

 

ItemRepository - ์ƒํ’ˆ ์ €์žฅ์†Œ

package hello.itemservice.domain.item;

import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Repository
public class ItemRepository {
    private static final Map<Long, Item> store = new HashMap<>();
    private static long sequence = 0L;

    public Item save(Item item)
    {
        item.setId(++sequence);
        store.put(item.getId(),item);
        return item;
    }

    public Item findById(Long id)
    {
        return store.get(id);
    }

    public List<Item> findAll()
    {
        return new ArrayList<>(store.values());
    }

    public void update(Long itemId, Item updateParam)
    {
        Item findItem = findById(itemId);
        findItem.setItemName(updateParam.getItemName());
        findItem.setPrice(updateParam.getPrice());
        findItem.setQuantity(updateParam.getQuantity());
    }

    public void clearStore()
    {
        sequence = 0L;
        store.clear();
    }
}

 

์ƒํ’ˆ ์„œ๋น„์Šค HTML

๋ถ€ํŠธ์ŠคํŠธ๋žฉ

HTML์„ ํŽธ๋ฆฌํ•˜๊ฒŒ ๊ฐœ๋ฐœํ•˜๊ธฐ ์œ„ํ•ด ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๋ถ€ํŠธ์ŠคํŠธ๋žฉ์ด๋ž€ ?

์›น์‚ฌ์ดํŠธ๋ฅผ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ฃผ๋Š” HTML, CSS, JS ํ”„๋ ˆ์ž„์›Œํฌ๋กœ, ํ•˜๋‚˜์˜ CSS๋กœ ํœด๋Œ€ํฐ, ํƒœ๋ธ”๋ฆฟ, ๋ฐ์Šคํฌํƒ‘๊นŒ์ง€ ๋‹ค์–‘ํ•œ ๊ธฐ๊ธฐ์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ์‰ฝ๊ฒŒ ์›น์‚ฌ์ดํŠธ๋ฅผ ์ œ์ž‘, ์œ ์ง€, ๋ณด์ˆ˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค.

 

 

  • ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ๊ณต์‹ ์‚ฌ์ดํŠธ์—์„œ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์„ ๋‹ค์šด๋ฐ›์•„ ์••์ถ•์„ ์ถœ๊ณ  bootstrap.min.css ๋ฅผ ๋ณต์‚ฌํ•ด์„œ ๋‹ค์Œ ํด๋”์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. 
/resources/static/css/bootstrap.min.css
  • ์ฐธ๊ณ ๋กœ /resources/static์— ๋„ฃ์–ด๋‘์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์Šคํ”„๋ง ๋ถ€ํŠธ๊ฐ€ ์ •์  ๋ฆฌ์†Œ์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
์ด๋ ‡๊ฒŒ ์ •์  ๋ฆฌ์†Œ์Šค๊ฐ€ ๊ณต๊ฐœ๋˜๋Š” /resources/static ํด๋”์— HTML์„ ๋„ฃ์–ด๋‘๋ฉด, ์‹ค์ œ ์„œ๋น„์Šค์—์„œ๋„ ๊ณต๊ฐœ๋ฉ๋‹ˆ๋‹ค. ์„œ๋น„์Šค๋ฅผ ์šด์˜ํ•œ๋‹ค๋ฉด ์ง€๊ธˆ์ฒ˜๋Ÿผ ๊ณต๊ฐœํ•  ํ•„์š”์—†๋Š” HTML์„ staticํด๋”์— ๋‘๋Š” ๊ฒƒ์„ ์ฃผ์˜ํ•ฉ์‹œ๋‹ค.

์„œ๋น„์Šค HTML - static

์ƒํ’ˆ ๋ชฉ๋ก - items.html

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <link href="../css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container" style="max-width: 600px">
  <div class="py-5 text-center">
    <h2>์ƒํ’ˆ ๋ชฉ๋ก</h2> </div>
  <div class="row">
    <div class="col">
      <button class="btn btn-primary float-end" onclick="location.href='addForm.html'" type="button">์ƒํ’ˆ
        ๋“ฑ๋ก</button> </div>
  </div>
  <hr class="my-4">
  <div>
    <table class="table">
      <thead>
      <tr>
        <th>ID</th>
        <th>์ƒํ’ˆ๋ช…</th> <th>๊ฐ€๊ฒฉ</th> <th>์ˆ˜๋Ÿ‰</th>

      </tr>
      </thead>
      <tbody>
      <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> <!-- /container -->
</body>
</html>

 

 

์ƒํ’ˆ ์ƒ์„ธ - item.html

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <link href="../css/bootstrap.min.css" rel="stylesheet">
    <style>
        .container {
            max-width: 560px;
        } </style>
</head>
<body>
<div class="container">
    <div class="py-5 text-center">
        <h2>์ƒํ’ˆ ์ƒ์„ธ</h2> </div>
    <div>
        <label for="itemId">์ƒํ’ˆ ID</label>
        <input type="text" id="itemId" name="itemId" class="form-control"
               value="1" readonly>
    </div> <div>
    <label for="itemName">์ƒํ’ˆ๋ช…</label>
    <input type="text" id="itemName" name="itemName" class="form-control"
           value="์ƒํ’ˆA" readonly> </div>
    <div>
        <label for="price">๊ฐ€๊ฒฉ</label>
        <input type="text" id="price" name="price" class="form-control"
               value="10000" readonly>
    </div> <div>
    <label for="quantity">์ˆ˜๋Ÿ‰</label>
    <input type="text" id="quantity" name="quantity" class="form-control"
           value="10" readonly>
</div>
    <hr class="my-4">
    <div class="row">
        <div class="col">
            <button class="w-100 btn btn-primary btn-lg" onclick="location.href='editForm.html'" type="button">์ƒํ’ˆ ์ˆ˜์ •</button>
        </div>
        <div class="col">
            <button class="w-100 btn btn-secondary btn-lg"
                    onclick="location.href='items.html'" type="button">๋ชฉ๋ก์œผ๋กœ</button> </div>
    </div>
</div> <!-- /container -->
</body>
</html>

์ƒํ’ˆ ๋“ฑ๋ก - addForm.html

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <link href="../css/bootstrap.min.css" rel="stylesheet">
    <style>
        .container {
            max-width: 560px;
        } </style>
</head>
<body>

<div class="container">

    <div class="py-5 text-center">
        <h2>์ƒํ’ˆ ๋“ฑ๋ก ํผ</h2>
    </div>

    <h4 class="mb-3">์ƒํ’ˆ ์ž…๋ ฅ</h4>

    <form action="item.html" method="post">
        <div>
            <label for="itemName">์ƒํ’ˆ๋ช…</label>
            <input type="text" id="itemName" name="itemName" class="form-control" placeholder="์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”"> </div>
        <div>
            <label for="price">๊ฐ€๊ฒฉ</label>
            <input type="text" id="price" name="price" class="form-control" placeholder="๊ฐ€๊ฒฉ์„ ์ž…๋ ฅํ•˜์„ธ์š”">
        </div> <div>
        <label for="quantity">์ˆ˜๋Ÿ‰</label>
        <input type="text" id="quantity" name="quantity" class="form-control" placeholder="์ˆ˜๋Ÿ‰์„ ์ž…๋ ฅํ•˜์„ธ์š”"> </div>
        <hr class="my-4">
        <div class="row">
            <div class="col">
                <button class="w-100 btn btn-primary btn-lg" type="submit">์ƒํ’ˆ ๋“ฑ๋ก</button> </div>
            <div class="col">
                <button class="w-100 btn btn-secondary btn-lg"
                        onclick="location.href='items.html'" type="button">์ทจ์†Œ</button> </div>
        </div>
    </form>
</div> <!-- /container -->
</body>
</html>

์ƒํ’ˆ ํŽธ์ง‘ - editForm.html

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <link href="../css/bootstrap.min.css" rel="stylesheet">
    <style>
        .container {
            max-width: 560px;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="py-5 text-center"> <h2>์ƒํ’ˆ ์ˆ˜์ • ํผ</h2>
    </div>
    <form action="item.html" method="post">
        <div>
            <label for="id">์ƒํ’ˆ ID</label>
            <input type="text" id="id" name="id" class="form-control" value="1" readonly>
        </div>

        <div>
            <label for="itemName">์ƒํ’ˆ๋ช…</label>
            <input type="text" id="itemName" name="itemName" class="form-control" value="์ƒํ’ˆA">
        </div>

        <div>
            <label for="price">๊ฐ€๊ฒฉ</label>
            <input type="text" id="price" name="price" class="form-control" value="10000">
        </div>

        <div>
            <label for="quantity">์ˆ˜๋Ÿ‰</label>
            <input type="text" id="quantity" name="quantity" class="form-control" value="10">
        </div>
        <hr class="my-4">
        <div class="row">
            <div class="col">
                <button class="w-100 btn btn-primary btn-lg" type="submit">์ €์žฅ
                </button>
            </div>
            <div class="col">
                <button class="w-100 btn btn-secondary btn-lg"
                        onclick="location.href='item.html'" type="button">์ทจ์†Œ</button> </div>
        </div>
    </form>
</div> <!-- /container -->
</body>
</html>

์ƒํ’ˆ ์ „์ฒด ๋ชฉ๋ก ์กฐํšŒ - ํƒ€์ž„๋ฆฌํ”„

์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์ปจํŠธ๋กค๋Ÿฌ์™€ ๋ทฐ ํ…œํ”Œ๋ฆฟ์„ ์ด์šฉํ•ด ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ฐธ๊ณ ๋กœ ๋ทฐ ํ…œํ”Œ๋ฆฟ์€ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์„ ์ด์šฉํ•œ HTML ํผ์— ํƒ€์ž„๋ฆฌํ”„๋ฅผ ์ถ”๊ฐ€ํ•ด ์‚ฌ์šฉํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

 

์šฐ์„  ์ปจํŠธ๋กค๋Ÿฌ๋ถ€ํ„ฐ ๋งŒ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

BasicItemController.java

@Slf4j
@Controller
@RequestMapping("/basic/items")
@RequiredArgsConstructor
public class BasicItemController {
    private final ItemRepository itemRepository;

    @GetMapping
    public String items(Model model)
    {
        List<Item> items = itemRepository.findAll();
        model.addAttribute("items",items);
        return "basic/items";
    }
    
    /*
    ํ…Œ์ŠคํŠธ์šฉ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€
     */

    @PostConstruct
    public void init()
    {
        itemRepository.save(new Item("ItemA",10000,10));
        itemRepository.save(new Item("ItemB",20000,20));
    }

์ปจํŠธ๋กค๋Ÿฌ ๋กœ์ง

@GetMapping
public String items(Model model)
{
    List<Item> items = itemRepository.findAll();
    model.addAttribute("items",items);
    return "basic/items";
}

localhost:8080/basic/items URL ํ˜ธ์ถœ

โ–ถ๏ธŽ itemRepository์—์„œ ๋ชจ๋“  ์ƒํ’ˆ์„ ์กฐํšŒํ•œ ๋‹ค์Œ์— List์— ๋‹ด์€ ํ›„ ๋ชจ๋ธ์— ์ถ”๊ฐ€ํ•˜๊ณ  ๋‚œ ๋’ค

โ–ถ๏ธŽ ๋ทฐ ํ…œํ”Œ๋ฆฟ("/basic/items")์„ ํ˜ธ์ถœ 

 

  • @RequiredArgsConstructor : final์ด ๋ถ™์€ ๋ฉค๋ฒ„๋ณ€์ˆ˜๋งŒ ์‚ฌ์šฉํ•ด์„œ ์ƒ์„ฑ์ž๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.
  • ์‹ค์งˆ์ ์œผ๋กœ ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ ๊ฒƒ๊ณผ ๊ฐ™์€ ํšจ๊ณผ๋ฅผ ๋ณด์ž…๋‹ˆ๋‹ค.
public BasicItemController(ItemRepository itemRepository) {
    this.itemRepository = itemRepository;

์ด๋ ‡๊ฒŒ ์ƒ์„ฑ์ž๊ฐ€ ๋”ฑ 1๊ฐœ๋งŒ ์žˆ์œผ๋ฉด ์Šคํ”„๋ง์€ ํ•ด๋‹น ์ƒ์„ฑ์ž์— @Autowired๋กœ ์˜์กด๊ด€๊ณ„๋ฅผ ์ž๋™์œผ๋กœ ์ฃผ์ž…ํ•ด์ค๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ final ํ‚ค์›Œ๋“œ๋ฅผ ๋นผ๋ฉด ์•ˆ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ItemRepository ์˜์กด๊ด€๊ณ„ ์ฃผ์ž…์ด ์•ˆ๋จ.

 

ํ…Œ์ŠคํŠธ์šฉ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€

ํ…Œ์ŠคํŠธ์šฉ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ํšŒ์› ๋ชฉ๋ก ๊ธฐ๋Šฅ์ด ์ •์ƒ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

@PostConstruct : ํ•ด๋‹น ๋นˆ์˜ ์˜์กด๊ด€๊ณ„๊ฐ€ ๋ชจ๋‘ ์ฃผ์ž…๋˜๊ณ  ๋‚˜๋ฉด ์ดˆ๊ธฐํ™” ์šฉ๋„๋กœ ํ˜ธ์ถœ

์—ฌ๊ธฐ์„œ๋Š” ๊ฐ„๋‹จํ•˜๊ฒŒ ํ…Œ์ŠคํŠธ์šฉ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

 

 

๋ทฐ ํ…œํ”Œ๋ฆฟ - /basic/items

/basic/items

์ด์ œ resources/static/html ์— ์ €์žฅํ•ด๋‘” ์ •์  HTML์„ resources/templates/basic์— items.html์„ ๋„ฃ๊ณ  thymeleaf ๋ทฐ ํ…œํ”Œ๋ฆฟ์„ ์ ์šฉ์‹œ์ผœ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="utf-8">
  <link href="/Users/kdo6301/Desktop/item-service/src/main/resources/static/css/bootstrap.min.css"
        th:href="@{/css/bootstrap.min.css}"
        rel="stylesheet">
</head>

<body>

<div class="container" style="max-width: 600px">

  <div class="py-5 text-center">
    <h2>์ƒํ’ˆ ๋ชฉ๋ก</h2>
  </div>

  <div class="row">
    <div class="col">
      <button class="btn btn-primary float-end"
              onclick="location.href='addForm.html'"
              th:onclick="|location.href='@{/basic/items/add}'|"
              type="button">์ƒํ’ˆ ๋“ฑ๋ก
      </button>
    </div>
  </div>

  <div class="row">
    <div class="col">
      <button class="btn btn-primary float-start"
              onclick="location.href='items.html'"
              th:onclick="|location.href='@{/basic/items/clear}'|"
              type="button">์ƒํ’ˆ ์ดˆ๊ธฐํ™”
      </button>
    </div>
  </div>

  <hr class="my-4">
  <div>
    <table class="table">

      <thead>
      <tr>
        <th>ID</th>
        <th>์ƒํ’ˆ๋ช…</th>
        <th>๊ฐ€๊ฒฉ</th>
        <th>์ˆ˜๋Ÿ‰</th>
      </tr>
      </thead>

      <tbody>
        <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}">์ƒํ’ˆ๋ช…</a>
          </td>

          <td th:text="${item.price}">10000</td>

          <td th:text="${item.quantity}">10</td>
        </tr>
      </tbody>

    </table>
  </div>
</div> <!-- /container -->
</body>
</html>

 

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="utf-8">
  <link href="/Users/kdo6301/Desktop/item-service/src/main/resources/static/css/bootstrap.min.css"
        th:href="@{/css/bootstrap.min.css}"
        rel="stylesheet">
</head>

Thymeleaf ์‚ฌ์šฉ ์„ ์–ธ

<html xmlns:th="http://www.thymeleaf.org">

 

์†์„ฑ ๋ณ€๊ฒฝ - th:href

<link href="/Users/kdo6301/Desktop/item-service/src/main/resources/static/css/bootstrap.min.css"
      th:href="@{/css/bootstrap.min.css}"
      rel="stylesheet">

href="value"๋ฅผ th:href="value"๋กœ ๋ณ€๊ฒฝ 

  • ํƒ€์ž„๋ฆฌํ”„ ๋ทฐํ…œํ”Œ๋ฆฟ์„ ๊ฑฐ์น˜๊ฒŒ ๋˜๋ฉด ์›๋ž˜ ๊ฐ’์„ th:xxx ๊ฐ’์œผ๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๊ฐ’์ด ์—†๋‹ค๋ฉด ์ƒˆ๋กœ ์ƒ์„ฑ.
  • HTML์„ ๊ทธ๋Œ€๋กœ ๋ณผ ๋•Œ์—๋Š” href ์†์„ฑ์ด ์‚ฌ์šฉ๋˜๊ณ  ๋ทฐ ํ…œํ”Œ๋ฆฟ์„ ๊ฑฐ์น˜๊ฒŒ ๋˜๋ฉด th:href์˜ ๊ฐ’์ด href ๊ฐ’์„ ๋Œ€์ฒดํ•˜๋ฉด์„œ ๋™์ ์œผ๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋Œ€๋ถ€๋ถ„์˜ HTML ์†์„ฑ์„ th:xxx ๋กœ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ

ํƒ€์ž„๋ฆฌํ”„ ํ•ต์‹ฌ

  • th:xxx๊ฐ€ ๋ถ™์€ ๋ถ€๋ถ„์€ ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง ๋ฐ ๊ธฐ์กด ๊ฒƒ ๋Œ€์ฒด.
  • th๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ์กด html์˜ ์†์„ฑ์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ
  • HTML์„ ํŒŒ์ผ๋กœ ์ง์ ‘ ์—ด์—ˆ์„ ๋•Œ์—๋Š” th:xxx๊ฐ€ ์žˆ์–ด๋„ ์›น ๋ธŒ๋ผ์šฐ์ €๋Š” th: ์†์„ฑ์„ ์•Œ์ง€ ๋ชปํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค.
  • ๋”ฐ๋ผ์„œ HTML์„ ํŒŒ์ผ ๋ณด๊ธฐ๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ ํ…œํ”Œ๋ฆฟ ๊ธฐ๋Šฅ๋„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์†์„ฑ ๋ณ€๊ฒฝ

1. ์ƒํ’ˆ๋“ฑ๋ก - th:onclick

<div class="row">
  <div class="col">
    <button class="btn btn-primary float-end"
            onclick="location.href='addForm.html'"
            th:onclick="|location.href='@{/basic/items/add}'|"
            type="button">์ƒํ’ˆ ๋“ฑ๋ก
    </button>
  </div>
</div>
  • onclick="location.href='addForm.html'" : static HTML์—์„œ ์ƒํ’ˆ๋“ฑ๋ก ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ ๋™์ž‘ 
  • th:onclick="|location.href='@{/basic/items/add}'|" : template HTML์—์„œ ์ƒํ’ˆ๋“ฑ๋ก ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ ๋™์ž‘( ์ƒํ’ˆ๋“ฑ๋ก ๋ทฐ ํ…œํ”Œ๋ฆฟ์ธ /basic/items/add ๋กœ ์ด๋™) 

์—ฌ๊ธฐ์„œ ์‚ฌ์šฉํ•œ ๋ฆฌํ„ฐ๋Ÿด ๋Œ€์ฒด ๋ฌธ๋ฒ• 

  • |~~~~| : ํƒ€์ž„๋ฆฌํ”„์—์„œ๋Š” ๋ฌธ์ž์™€ ํ‘œํ˜„์‹ ๋“ฑ์€ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋”ํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • ์ด๋ฅผํ…Œ๋ฉด ์ด๋ ‡๊ฒŒ 
    • <span th:text="'Welcome to our application, ' + ${user.name} + '!'"
  • ํ•˜์ง€๋งŒ ๋ฆฌํ„ฐ๋Ÿด ๋Œ€์ฒด ๋ฌธ๋ฒ• |~~~~|์„ ์‚ฌ์šฉํ•˜๋ฉด ๋”ํ•˜๊ธฐ ์—†์ด ํŽธ๋ฆฌํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • <span th:text="|Welcome to our application, ${user.name}!|"

์—ฌ๊ธฐ์„œ ์‚ฌ์šฉํ•œ URL ๋งํฌ ํ‘œํ˜„์‹

  • @{~~~} : ํƒ€์ž„๋ฆฌํ”„๋Š” URL ๋งํฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ @{~~~~}๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฑธ URL ๋งํฌ ํ‘œํ˜„์‹์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

 

2. ์ƒํ’ˆ ์ดˆ๊ธฐํ™” - th:onclick

<div class="row">
  <div class="col">
    <button class="btn btn-primary float-start"
            onclick="location.href='items.html'"
            th:onclick="|location.href='@{/basic/items/clear}'|"
            type="button">์ƒํ’ˆ ์ดˆ๊ธฐํ™”
    </button>
  </div>
</div>

์ƒํ’ˆ์ดˆ๊ธฐํ™” ๋ฒ„ํŠผ ํด๋ฆญ์‹œ /basic/items/clear URL ํ˜ธ์ถœ

โ–ถ๏ธŽ @GetMapping("/basic/items/clear") ๋™์ž‘ 

@GetMapping("/clear")
public String clearList()
{
    itemRepository.clearStore();
    return "basic/items";
}

itemRepository ์ดˆ๊ธฐํ™” ํ•œ ํ›„ basic/items ๋ทฐ ํ…œํ”Œ๋ฆฟ ํ˜ธ์ถœ

 

3. ์ƒํ’ˆ ๋ชฉ๋ก ์กฐํšŒ - th:each(๋ฐ˜๋ณต ์ถœ๋ ฅ)

<tbody>
  <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}">์ƒํ’ˆ๋ช…</a>
    </td>

    <td th:text="${item.price}">10000</td>

    <td th:text="${item.quantity}">10</td>
  </tr>
</tbody>

 

์—ฌ๊ธฐ์„œ ์‚ฌ์šฉํ•œ ๋ฐ˜๋ณต ์ถœ๋ ฅ ๋ฌธ๋ฒ• th:each

  • <th th:each="item : ${items}">
  • ๋ฐ˜๋ณต์€ th:each๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ชจ๋ธ์— ํฌํ•จ๋œ items ์ปฌ๋ ‰์…˜ ๋ฐ์ดํ„ฐ๊ฐ€ item ๋ณ€์ˆ˜์— ํ•˜๋‚˜์”ฉ ํฌํ•จ๋˜๊ณ , ๋ฐ˜๋ณต๋ฌธ ์•ˆ์—์„œ item ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ปฌ๋ ‰์…˜์˜ ์ˆ˜ ๋งŒํผ <tr>..</tr>๊ฐ€ ํ•˜์œ„ ํƒœ๊ทธ๋ฅผ ํฌํ•จํ•ด์„œ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ์‚ฌ์šฉํ•œ ๋งํฌ ํ‘œํ˜„์‹ @{.../{value}(value=${param.value})

  • th:href="@{/basic/items/{itemId}(itemId=${item.id})}"
  • URL ๋งํฌ ํ‘œํ˜„์‹์„ ๊ฒฝ๋กœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฒฝ๋กœ๋ณ€์ˆ˜ {itemId} ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • ํšŒ์› ID๋‚˜ ์ƒํ’ˆ๋ช…์„ ํด๋ฆญํ•˜๋ฉด /basic/items/{ItemId} ๋งํฌ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค - ์ƒํ’ˆ ์ƒ์„ธ ํผ
  • ex) th:href="@{/basic/items/{itemId}(itemId=${item.id}, query = 'test')}"
  • ์ƒ์„ฑ ๋งํฌ : http://localhost:8080/basic/items/1?query=test

์—ฌ๊ธฐ์„œ ์‚ฌ์šฉํ•œ ๋‚ด์šฉ ๋ณ€๊ฒฝ ๋ฌธ๋ฒ• th:text

  • <td th:text="${item.price}">10000</td>
  • ๋‚ด์šฉ์˜ ๊ฐ’(10000)์„ th:text์˜ ๊ฐ’์œผ๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ์‚ฌ์šฉํ•œ ๋ณ€์ˆ˜ ํ‘œํ˜„์‹ ${..}

  • <td th:text="${item.price}">10000</td>
  • ๋ชจ๋ธ์— ํฌํ•จ๋œ ๊ฐ’์ด๋‚˜, ํƒ€์ž„๋ฆฌํ”„ ๋ณ€์ˆ˜๋กœ ์„ ์–ธํ•œ ๊ฐ’์„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ”„๋กœํผํ‹ฐ ์ ‘๊ทผ๋ฒ• ์‚ฌ์šฉ (item.getPrice())
โ—๏ธํƒ€์ž„๋ฆฌํ”„๋Š” ์ˆœ์ˆ˜ HTML ํŒŒ์ผ์„ ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ์—ด์–ด๋„ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ณ , ์„œ๋ฒ„๋ฅผ ํ†ตํ•ด ๋ทฐ ํ…œํ”Œ๋ฆฟ์„ ๊ฑฐ์ณ ๋™์ ์œผ๋กœ ๋ณ€๊ฒฝ๋œ ๊ฒฐ๊ณผ๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์ˆœ์ˆ˜ HTML ์„ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜๋ฉด์„œ ๋ทฐํ…œํ”Œ๋ฆฟ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํƒ€์ž„๋ฆฌํ”„์˜ ํŠน์ง•์„ ๋„ค์ธ„๋Ÿด ํ…œํ”Œ๋ฆฟ ์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์ƒํ’ˆ ์ƒ์„ธ ์กฐํšŒ - ํƒ€์ž„๋ฆฌํ”„

BasicItemController์— ์ปจํŠธ๋กค๋Ÿฌ ์ถ”๊ฐ€

@Slf4j
@Controller
@RequestMapping("/basic/items")
@RequiredArgsConstructor
public class BasicItemController {
    private final ItemRepository itemRepository;
    
    @GetMapping("/{itemId}")
    public String item(@PathVariable Long itemId, Model model)
    {
        Item item = itemRepository.findById(itemId);
        model.addAttribute("item",item);
        return "basic/item";
    }
    
  	...
}

์ปจํŠธ๋กค๋Ÿฌ ๋กœ์ง

โ–ถ๏ธŽ /basic/items/{itemId} URL ํ˜ธ์ถœ

โ–ถ๏ธŽ @PathVariable ๊ฒฝ๋กœ๋ณ€์ˆ˜๋กœ ๋„˜์–ด์˜จitemid๋ฅผ ํ†ตํ•ด Item์„ ์ฐพ์•„ model์— item์„ ๋‹ด์Šต๋‹ˆ๋‹ค.

โ–ถ๏ธŽ View ํ…œํ”Œ๋ฆฟ basic/item ํ˜ธ์ถœ 

 

๋ทฐ ํ…œํ”Œ๋ฆฟ 

/basic/items/{itemId}

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link href="/Users/kdo6301/Desktop/item-service/src/main/resources/static/css/bootstrap.min.css"
          th:href="@{/css/bootstrap.min.css}"
          rel="stylesheet">
    <style>
        .container {
            max-width: 560px;
        } </style>
</head>
<body>
<div class="container">
    <div class="py-5 text-center">
        <h2>์ƒํ’ˆ ์ƒ์„ธ</h2>
    </div>

    <!--์ถ”๊ฐ€-->
    <h2 th:if="${param.status}" th:text="'์ €์žฅ ์™„๋ฃŒ'"></h2>

    <div>
        <label for="itemId">์ƒํ’ˆ ID</label>
        <input type="text" id="itemId" name="itemId" class="form-control"
               value="1"
               th:value="${item.id}"
               readonly>
    </div>

    <div>
    <label for="itemName">์ƒํ’ˆ๋ช…</label>
    <input type="text" id="itemName" name="itemName" class="form-control"
           th:value="${item.itemName}"
           value="์ƒํ’ˆA" readonly>
    </div>

    <div>
        <label for="price">๊ฐ€๊ฒฉ</label>
        <input type="text" id="price" name="price" class="form-control"
               th:value="${item.price}"
               value="10000" readonly>
    </div>

    <div>
        <label for="quantity">์ˆ˜๋Ÿ‰</label>
        <input type="text" id="quantity" name="quantity" class="form-control"
               th:value="${item.quantity}"
               value="10" readonly>
    </div>

    <hr class="my-4">
    <div class="row">
        <div class="col">
            <button class="w-100 btn btn-primary btn-lg"
                    th:onclick="|location.href='@{/basic/items/{itemId}/edit
                    (itemId=${item.getId()})}'|"
                    onclick="location.href='editForm.html'" type="button">์ƒํ’ˆ ์ˆ˜์ •</button>
        </div>
        <div class="col">
            <button class="w-100 btn btn-secondary btn-lg"
                    th:onclick="|location.href='@{/basic/items}'|"
                    onclick="location.href='items.html'" type="button">๋ชฉ๋ก์œผ๋กœ</button> </div>
    </div>
</div> <!-- /container -->
</body>
</html>

 

์—ฌ๊ธฐ์„œ ์‚ฌ์šฉํ•œ ์†์„ฑ ๋ณ€๊ฒฝ ๋ฌธ๋ฒ• th:value

  • th:value="${item.id}"
  • ๋ชจ๋ธ์— ์žˆ๋Š” item์ •๋ณด๋ฅผ ํš๋“ํ•˜๊ณ  ํ”„๋กœํผํ‹ฐ ์ ‘๊ทผ๋ฒ•์œผ๋กœ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.
  • value ์†์„ฑ์„ th:value ์†์„ฑ์œผ๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
<hr class="my-4">
<div class="row">
    <div class="col">
        <button class="w-100 btn btn-primary btn-lg"
                th:onclick="|location.href='@{/basic/items/{itemId}/edit
                (itemId=${item.getId()})}'|"
                onclick="location.href='editForm.html'" type="button">์ƒํ’ˆ ์ˆ˜์ •</button>
    </div>
    <div class="col">
        <button class="w-100 btn btn-secondary btn-lg"
                th:onclick="|location.href='@{/basic/items}'|"
                onclick="location.href='items.html'" type="button">๋ชฉ๋ก์œผ๋กœ</button> </div>
</div>
  • th:onclick = "|location.href='@{/basic/items/{itemId}/edit (itemId = ${item.id})}'|"
  • ์ƒํ’ˆ ์ˆ˜์ • ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ /basic/items/{itemid}/edit์œผ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. 
  • th:onclick="|location.href='@{/basic/items}'|"
  • ๋ชฉ๋ก์œผ๋กœ ๋ฒ„ํŠผ ํด๋ฆญ์‹œ /basic/items๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.

์ƒํ’ˆ ๋“ฑ๋ก - ํƒ€์ž„๋ฆฌํ”„

BasicItemController์— ์ปจํŠธ๋กค๋Ÿฌ ์ถ”๊ฐ€

@Slf4j
@Controller
@RequestMapping("/basic/items")
@RequiredArgsConstructor
public class BasicItemController {
    @GetMapping("/add")
    public String addForm()
    {
        return "basic/addForm";
    }

	
    //    @PostMapping("/add")
//    public String addItemV1(@RequestParam String itemName,
//                            @RequestParam int price,
//                            @RequestParam Integer quantity,
//                            Model model)
//    {
//        Item item = new Item();
//        item.setItemName(itemName);
//        item.setPrice(price);
//        item.setQuantity(quantity);
//
//        itemRepository.save(item);
//
//        model.addAttribute("item",item);
//
//        return "basic/item";
//    }
//

//    @PostMapping("/add")
//    public String addItemV2(@ModelAttribute("item") Item item, Model model)
//    {
//        log.info("item = {}",item);
//        log.info("itemName = {} , itemPrice = {} , itemQuantity = {} ",
//                item.getItemName(),item.getPrice(),item.getQuantity());
//        itemRepository.save(item);
//
//        return "basic/item";
//    }

    /*
    @ModelAttribute์˜ ์ด๋ฆ„์„ ์ƒ๋žตํ•˜๋ฉด ๋ชจ๋ธ์— ์ €์žฅ๋  ๋•Œ ํด๋ž˜์Šค๋ช…์„ ์†Œ๋ฌธ์ž๋กœ ๋ณ€๊ฒฝํ•ด์„œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

     */
//    @PostMapping("/add")
//    public String addItemV3(@ModelAttribute Item item, Model model)
//    {
//        log.info("item = {}",item);
//        log.info("itemName = {} , itemPrice = {} , itemQuantity = {} ",
//                item.getItemName(),item.getPrice(),item.getQuantity());
//
//        itemRepository.save(item);
//        return "basic/item";
//    }

//    /*
//    @ModelAttribute ์ž์ฒด๋„ ์ƒ๋žต ๊ฐ€๋Šฅ.
//    ๊ฐ์ฒด์˜ ๊ฒฝ์šฐ ์ž๋™์œผ๋กœ ModelAttribute ์ ์šฉ
//    ๋‹จ์ˆœ ํƒ€์ž…์˜ ๊ฒฝ์šฐ @RequestParam ์ ์šฉ
//     */
//    @PostMapping("/add")
//    public String addItemV4(Item item)
//    {
//        itemRepository.save(item);
//        return "redirect:/basic/items/" + item.getId();
//    }

    @PostMapping("/add")
    public String addItemV6(Item item, RedirectAttributes redirectAttributes)
    {
        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId",savedItem.getId());
        redirectAttributes.addAttribute("status",true);
        return "redirect:/basic/items/{itemId}";
    }

}

 

์ƒํ’ˆ ๋“ฑ๋ก ํผ์€ @GetMapping, @PostMapping ๋‘ ๊ฐœ๋กœ ๋ฐ›์Šต๋‹ˆ๋‹ค.

 

@GetMapping์€ /basic/items/add URL๋กœ ์ด๋™ ์‹œ addForm ๋ทฐ ํ…œํ”Œ๋ฆฟ์„ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๊ณ ,

@PostMapping์€ /basic/items/add URL( ๋ทฐํ…œํ”Œ๋ฆฟ - /basic/addForm )์—์„œ HTML ํผ์„ ์ด์šฉํ•ด ์ƒํ’ˆ ์ •๋ณด ์ž…๋ ฅ ํ›„ ๋“ฑ๋กํ•  ๋•Œ์— ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ

 

 

๋ทฐ ํ…œํ”Œ๋ฆฟ

/basic/items/add ( ๋ทฐํ…œํ”Œ๋ฆฟ์€ basic/addForm )

 

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">

    <head>
        <meta charset="utf-8">
        <link href="/Users/kdo6301/Desktop/item-service/src/main/resources/static/css/bootstrap.min.css"
              th:href="@{/css/bootstrap.min.css}"
              rel="stylesheet">
        <style>
            .container {
                max-width: 560px;
            } </style>
    </head>

    <body>

        <div class="container">

            <div class="py-5 text-center">
                <h2>์ƒํ’ˆ ๋“ฑ๋ก ํผ</h2>
            </div>

            <h4 class="mb-3">์ƒํ’ˆ ์ž…๋ ฅ</h4>

            <form action="item.html" th:action method="post">
                <div>
                    <label for="itemName">์ƒํ’ˆ๋ช…</label>
                    <input type="text" id="itemName" name="itemName" class="form-control" placeholder="์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”"> </div>
                <div>
                    <label for="price">๊ฐ€๊ฒฉ</label>
                    <input type="text" id="price" name="price" class="form-control" placeholder="๊ฐ€๊ฒฉ์„ ์ž…๋ ฅํ•˜์„ธ์š”">
                </div>

                <div>
                    <label for="quantity">์ˆ˜๋Ÿ‰</label>
                    <input type="text" id="quantity" name="quantity" class="form-control" placeholder="์ˆ˜๋Ÿ‰์„ ์ž…๋ ฅํ•˜์„ธ์š”">
                </div>

                <hr class="my-4">
                <div class="row">
                    <div class="col">
                        <button class="w-100 btn btn-primary btn-lg" type="submit">์ƒํ’ˆ
                            ๋“ฑ๋ก</button> </div>
                    <div class="col">
                        <button class="w-100 btn btn-secondary btn-lg"
                                th:onclick="|location.href='@{/basic/items}'|"
                                onclick="location.href='items.html'" type="button">์ทจ์†Œ</button> </div>
                </div>
            </form>
        </div> <!-- /container -->

    </body>
</html>

 

์—ฌ๊ธฐ์„œ ์‚ฌ์šฉํ•œ ์†์„ฑ ๋ณ€๊ฒฝ th:action

  • HTML form์—์„œ action์— ๊ฐ’์ด ์—†์œผ๋ฉด ํ˜„์žฌ URL์— ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค
  • ์ƒํ’ˆ ๋“ฑ๋ก ํผ์˜ URL๊ณผ ์‹ค์ œ ์ƒํ’ˆ ๋“ฑ๋ก์„ ์ฒ˜๋ฆฌํ•˜๋Š” URL์„ ๋˜‘๊ฐ™์ด ๋งž์ถ”๊ณ  HTTP๋ฉ”์†Œ๋“œ๋กœ ๋‘ ๊ธฐ๋Šฅ์„ ๊ตฌ๋ถ„ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ์ƒํ’ˆ ๋“ฑ๋ก ํผ : GET - /basic/items/add
  • ์ƒํ’ˆ ๋“ฑ๋ก ์ฒ˜๋ฆฌ : POST - /basic/items/add
  • ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ•˜๋‚˜์˜ URL๋กœ ๋“ฑ๋ก ํผ๊ณผ, ๋“ฑ๋ก ์ฒ˜๋ฆฌ๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ทจ์†Œ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ /basic/items ๋กœ ์ด๋™ 

 

BasicItemController์—์„œ ์ƒํ’ˆ ๋“ฑ๋ก์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์•Œ์•„๋ด…์‹œ๋‹ค.

v1 - @RequestParam์œผ๋กœ ์ฒ˜๋ฆฌ

@PostMapping("/add")
public String addItemV1(@RequestParam String itemName,
                        @RequestParam int price,
                        @RequestParam Integer quantity,
                        Model model)
{
    Item item = new Item();
    item.setItemName(itemName);
    item.setPrice(price);
    item.setQuantity(quantity);

    itemRepository.save(item);

    model.addAttribute("item",item);

    return "basic/item";
}

@RequestParam์œผ๋กœ addForm์—์„œ post๋กœ ๋ฐ›์€ itemName, price, quantity๋ฅผ ํ†ตํ•ด item๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ  repository์— ๋‹ด๊ณ , ๋ชจ๋ธ์— ๋‹ด์€ ๋’ค basic/item ๋ทฐํ…œํ”Œ๋ฆฟ์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

 

๋ทฐํ…œํ”Œ๋ฆฟ /basic/addForm (URL - basic/items/add) (์ขŒ) ์—์„œ ์ •๋ณด ์ž…๋ ฅ ํ›„ ๋“ฑ๋ก ๋ฒ„ํŠผ์„ ํ†ตํ•ด POST ์ „์†ก ํ›„ ํ™”๋ฉด (์šฐ)

 

์ค‘์š” : ์—ฌ๊ธฐ์„œ๋Š” ์ƒํ’ˆ ์ƒ์„ธ์—์„œ ์‚ฌ์šฉํ•œ item.html ๋ทฐํ…œํ”Œ๋ฆฟ์„ ๊ทธ๋Œ€๋กœ ์žฌํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.

 

v2 - @ModelAttribute ์‚ฌ์šฉ

@PostMapping("/add")
public String addItemV2(@ModelAttribute("item") Item item, Model model)
{
    log.info("item = {}",item);
    log.info("itemName = {} , itemPrice = {} , itemQuantity = {} ",
            item.getItemName(),item.getPrice(),item.getQuantity());
    itemRepository.save(item);
    model.addAttribute("item",item); // ์ƒ๋žต ๊ฐ€๋Šฅ 
    
    return "basic/item";
}

@ModelAttribute๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด @RequestParam์œผ๋กœ ๋ณ€์ˆ˜ ํ•˜๋‚˜ํ•˜๋‚˜๋ฅผ ๋ฐ›์•„์„œ Item์„ ์ƒ์„ฑํ•˜๋Š” ๊ณผ์ •์„ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ model์— ๊ฐ์ฒด๋ฅผ ๋‹ด์•„ ๋ทฐํ…œํ”Œ๋ฆฟ ํ˜ธ์ถœํ–ˆ์Šต๋‹ˆ๋‹ค.

 

์ฐธ๊ณ ๋กœ

  • model.addAttribute("item",item) ์ƒ๋žต์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. @ModelAttribute๋Š” Model์— ModelAttribute๋กœ ์ง€์ •ํ•œ ๊ฐ์ฒด๋ฅผ ์ž๋™์œผ๋กœ ๋„ฃ์–ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • @ModelAttribute("item") โ–ถ๏ธŽ @ModelAttribute ๋กœ ๋ณ€๊ฒฝ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
  • @ModelAttribute ์ž์ œ๋ฅผ ์ƒ๋žต๋„ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ๊ถŒ์žฅํ•˜์ง„ ์•Š์Œ - ๋ช…์‹œ์ ์ด์ง€ ์•Š์Œ
  • ๋ชจ๋ธ์— ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด์„ ๋•Œ๋Š” ์ด๋ฆ„์ด ํ•„์š”ํ•œ๋ฐ ์ด ์ด๋ฆ„๊ณผ @ModelAttribute ์— ์ง€์ •ํ•œ Item item ์ด ๊ฐ™๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” ์ƒ๋žต์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. 

์ƒํ’ˆ ์ˆ˜์ • - ํƒ€์ž„๋ฆฌํ”„

์ƒํ’ˆ ์ˆ˜์ • ํผ ์ปจํŠธ๋กค๋Ÿฌ

BasicItemController ์— ์ถ”๊ฐ€

@GetMapping("/{itemId}/edit")
public String editForm(@PathVariable Long itemId, Model model)
{
    Item item = itemRepository.findById(itemId);
    model.addAttribute("item",item);
    return "basic/editForm";
}

@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @ModelAttribute Item item)
{
    itemRepository.update(itemId, item);
    return "redirect:/basic/items/{itemId}";
}

1. editForm

/basic/items/{itemId}/edit URL ํ˜ธ์ถœ ์‹œ ๊ฒฝ๋กœ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•ด itemId๋ฅผ ์ฐพ๊ณ  ๋ชจ๋ธ์— ๋‹ด์€ ํ›„ 

basic/editForm ๋ทฐ ํ…œํ”Œ๋ฆฟ์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

 

2. edit

  • /basic/items/{itemId}/edit URL (๋ทฐ ํ…œํ”Œ๋ฆฟ์€ basic/editForm) ์—์„œ Post๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ผ ๋•Œ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
  • ํผ์„ ํ†ตํ•ด ์ž…๋ ฅํ•œ ์ •๋ณด๋ฅผ Item์— ๋‹ด๊ณ  ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๊ฒฝ๋กœ๋ณ€์ˆ˜ itemId๋ฅผ ํ†ตํ•ด repository์— ํ•ด๋‹น id์˜ item์˜ ์ •๋ณด๋ฅผ ๋ชจ๋ธ์— ๋‹ด๊ธด Item์˜ ์ •๋ณด๋กœ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
  • ๊ทธ๋ฆฌ๊ณ  ๋งˆ์ง€๋ง‰์— ๋ทฐ ํ…œํ”Œ๋ฆฟ์„ ํ˜ธ์ถœํ•˜๋Š” ๋Œ€์‹ ์— ์ƒํ’ˆ ์ƒ์„ธํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•˜๋„๋ก ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋ฅผ ํ˜ธ์ถœํ–ˆ์Šต๋‹ˆ๋‹ค.
  • HTML ์ „์†ก์€ PUT, PATCH๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๊ณ , POST, GET๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ˆ˜์ • ์ž‘์—…์—์„œ POST ๋ฐฉ์‹์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

์Šคํ”„๋ง์€ redirect:/...์œผ๋กœ ํŽธ๋ฆฌํ•˜๊ฒŒ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

  • ์ปจํŠธ๋กค๋Ÿฌ์— ๋งคํ•‘๋œ @PathVariable์˜ ๊ฐ’์€ redirect์—๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

๋ฆฌ๋‹ค์ด๋ ‰์…˜ ์ฐธ๊ณ  :

https://rlaehddnd0422.tistory.com/44#3xx%20(Redirection)%20-%20%EB%A6%AC%EB%8B%A4%EC%9D%B4%EB%A0%89%EC%85%98-1 

 

HTTP ์ƒํƒœ์ฝ”๋“œ / 1~5XX

์ƒํƒœ์ฝ”๋“œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณด๋‚ธ ์š”์ฒญ์˜ ์ฒ˜๋ฆฌ ์ƒํƒœ๋ฅผ ์‘๋‹ต์—์„œ ์•Œ๋ ค์ฃผ๋Š” ๊ธฐ๋Šฅ 1xx (Informational) : ์š”์ฒญ์ด ์ˆ˜์‹ ๋˜์–ด ์ฒ˜๋ฆฌ์ค‘ 2xx (Successful) : ์š”์ฒญ ์ •์ƒ ์ฒ˜๋ฆฌ 3xx (Redirection) : ์š”์ฒญ์„ ์™„๋ฃŒํ•˜๋ ค๋ฉด ์ถ”๊ฐ€ ํ–‰๋™

rlaehddnd0422.tistory.com

basic/editForm ๋ทฐ ํ…œํ”Œ๋ฆฟ

<!DOCTYPE HTML>
<html 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"
    >
    <style>
        .container {
            max-width: 560px;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="py-5 text-center"> <h2>์ƒํ’ˆ ์ˆ˜์ • ํผ</h2>
    </div>
    <form action="item.html" th:action method="post">
        <div>
            <label for="id">์ƒํ’ˆ ID</label>
            <input type="text" id="id" name="id" class="form-control"
                   value="1"
                   th:value="${item.id}"
                   readonly>
        </div>

        <div>
            <label for="itemName">์ƒํ’ˆ๋ช…</label>
            <input type="text" id="itemName" name="itemName" class="form-control"
                   value="์ƒํ’ˆA"
                   th:value="${item.itemName}"
            >
        </div>

        <div>
            <label for="price">๊ฐ€๊ฒฉ</label>
            <input type="text" id="price" name="price" class="form-control"
                   value="10000"
                    th:value="${item.price}">
        </div>
        <div>
            <label for="quantity">์ˆ˜๋Ÿ‰</label>
            <input type="text" id="quantity" name="quantity" class="form-control"
                   value="10"
                    th:value="${item.quantity}">
        </div>

        <hr class="my-4">
        <div class="row">
            <div class="col">
                <button class="w-100 btn btn-primary btn-lg" type="submit">์ €์žฅ
                </button>
            </div>

            <div class="col">
                <button class="w-100 btn btn-secondary btn-lg"
                        onclick="location.href='item.html'"
                        th:onclick="|location.href='@{/basic/items/{itemId}
                        (itemId=${item.id})}'|"
                        type="button">์ทจ์†Œ</button> </div>
        </div>
    </form>
</div> <!-- /container -->
</body>
</html>

 

๋“ฑ๋ก ํผ๊ณผ ์œ ์‚ฌํ•ด์„œ ํŠน๋ณ„ํžˆ ์งš๊ณ  ๋„˜์–ด๊ฐˆ ๋ถ€๋ถ„์ด ์—†์Šต๋‹ˆ๋‹ค. 


PRG (Post/Redirect/Get)

์‚ฌ์‹ค ์ง€๊ธˆ๊นŒ์ง€ ์ง„ํ–‰ํ•œ ์ƒํ’ˆ ๋“ฑ๋ก ์ฒ˜๋ฆฌ ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์‹ฌ๊ฐํ•œ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.  ์ƒํ’ˆ ๋“ฑ๋ก์„ ์™„๋ฃŒํ•˜๊ณ  ์›น ๋ธŒ๋ผ์šฐ์ €์˜ ์ƒˆ๋กœ๊ณ ์นจ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ๋ณดใ…•๋ณด๋ฉด ์ƒํ’ˆ์ด ๊ณ„์†ํ•ด์„œ ์ค‘๋ณต ๋“ฑ๋ก๋˜๋Š”๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

 

์ฒซ๋ฒˆ์งธ ๋“ฑ๋ก ํ›„ / ์ƒˆ๋กœ๊ณ ์นจ ํ›„  

์›น ๋ธŒ๋ผ์šฐ์ €์˜ ์ƒˆ๋กœ๊ณ ์นจ์€ ๋งˆ์ง€๋ง‰์— ์„œ๋ฒ„์— ์ „์†กํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

์ƒํ’ˆ ๋“ฑ๋ก ํผ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ค๊ฐ›๊ณ  ์ €์žฅ์„ ์„ ํƒํ•˜๋ฉด POST /add + ์ƒํ’ˆ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„๋กœ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

์ด ์ƒํƒœ์—์„œ ์ƒˆ๋กœ๊ณ ์นจ์„ ๋˜ ์„ ํƒํ•˜๋ฉด ๋งˆ์ง€๋ง‰์— ์ „์†กํ•œ POST /add + ์ƒํ’ˆ ๋ฐ์ดํ„ฐ๋ฅด ๋‹ค์‹œ ์ „์†กํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. 

๊ทธ๋ž˜์„œ ๋‚ด์šฉ์€ ๊ฐ™๊ณ  ID๋งŒ ๋‹ค๋ฅธ ์ƒํ’ˆ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ณ„์† ์Œ“์ด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

์ด ๋ฌธ์ œ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?

 

์ƒํ’ˆ ์ €์žฅ ํ›„์— ๋ทฐ ํ…œํ”Œ๋ฆฟ์œผ๋กœ ์ด๋™ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์ƒํ’ˆ ์ƒ์„ธํ™”๋ฉด์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋ฅผ ํ˜ธ์ถœํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• : ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋กœ ๋ณ€๊ฒฝ

@PostMapping("/add")
public String addItemV4(Item item)
{
    itemRepository.save(item);
    return "redirect:/basic/items/" + item.getId();
}

 

์ด๋Ÿฐ ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ์‹์„ PRG( POST -> Redirect -> GET) ๋ฐฉ์‹ ์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

 

ํ•˜์ง€๋งŒ ์—ฌ๊ธฐ์„œ item.getId()๋ฅผ ํ†ตํ•ด URL์— ๋ณ€์ˆ˜๋ฅผ ๋”ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ URL ์ธ์ฝ”๋”ฉ์ด ์•ˆ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์œ„ํ—˜ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์— ์„ค๋ช…ํ•˜๋Š” RedirectAttributes๋ฅผ ์‚ฌ์šฉํ•ฉ์‹œ๋‹ค.

 


RedirectAttributes

์ƒํ’ˆ ์ €์žฅ ํ›„ ์ƒํ’ˆ ์ƒ์„ธํ™”๋ฉด์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•œ ๊ฒƒ ๊นŒ์ง€๋Š” ์ข‹์•˜์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํด๋ผ์ด์–ธํŠธ ์ž…์žฅ์—์„œ ์ €์žฅ์ด ์ž˜ ๋œ ๊ฒƒ์ธ์ง€ ์•ˆ ๋œ ๊ฒƒ์ธ์ง€ ํ™•์‹ ์ด ๋“ค์ง€ ์•Š์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ €์žฅ์ด ์ž˜ ๋˜์—ˆ์œผ๋ฉด ์ƒํ’ˆ ์ƒ์„ธ ํ™”๋ฉด์— "์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค" ๋ผ๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์š”๊ตฌ์‚ฌํ•ญ๊นŒ์ง€ ์ถ”๊ฐ€ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

BasicItemController ์— ์ถ”๊ฐ€ 

@PostMapping("/add")
public String addItemV6(Item item, RedirectAttributes redirectAttributes)
{
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId",savedItem.getId());
    redirectAttributes.addAttribute("status",true);
    return "redirect:/basic/items/{itemId}";
}

๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ํ•  ๊ฒฝ์šฐ ๊ฐ„๋‹จํžˆ status=true๋ฅผ ์ถ”๊ฐ€ํ•ด๋ด…์‹œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ทฐ ํ…œํ”Œ๋ฆฟ์—์„œ ์ด ๊ฐ’์ด ์žˆ์œผ๋ฉด "์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." ๋ผ๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์‹คํ–‰ํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ต๋‹ˆ๋‹ค.

localhost:8080/basic/items/3?status=true

 

RedirectAttributes๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด

URL ์ธ์ฝ”๋”ฉ๋„ ํ•ด์ฃผ๊ณ ,

pathVariable๊ณผ ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๊นŒ์ง€ ์ฒ˜๋ฆฌํ•ด์ค๋‹ˆ๋‹ค.

  • redirect:/basic/items/{itemid}
  • {itemId} ๋Š” redirectAttribute์— ์ถ”๊ฐ€ํ•œ itemId, ๋‚˜๋จธ์ง€๋Š” ์ฟผ๋ฆฌํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ฒ˜๋ฆฌ ?status=true

/basic/items/{itemId}๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ์‹œ /basic/items/{itemId} URL ํ˜ธ์ถœํ•˜๋ฉด


@GetMapping("/{itemId}")
public String item(@PathVariable Long itemId, Model model)
{
    Item item = itemRepository.findById(itemId);
    model.addAttribute("item",item);
    return "basic/item";
}

์•„์ด๋””๋ฅผ ์ฐพ๊ณ  ๋ชจ๋ธ์— ๋‹ด์€ ํ›„ basic/item ๋ทฐํ…œํ”Œ๋ฆฟ์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

 

basic/item ๋ทฐํ…œํ”Œ๋ฆฟ์˜ ์ƒํ’ˆ์ƒ์„ธ ์•„๋ž˜์— ๋ฉ”์‹œ์ง€๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.


<!--์ถ”๊ฐ€-->
<h2 th:if="${param.status}" th:text="'์ €์žฅ ์™„๋ฃŒ'"></h2>

 

  • th:if๋Š” ํ•ด๋‹น ์กฐ๊ฑด์ด ์ฐธ์ด๋ฉด ์‹คํ–‰ํ•˜๋Š” ํƒ€์ž„๋ฆฌํ”„ ์กฐ๊ฑด๋ฌธ์ž…๋‹ˆ๋‹ค.
  • ${param.status}๋Š” ํƒ€์ž„๋ฆฌํ”„์—์„œ ์ฟผ๋ฆฌํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํŽธ๋ฆฌํ•˜๊ฒŒ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์œผ๋กœ ์›๋ž˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋ชจ๋ธ์— ์ง์ ‘ ๋‹ด๊ณ  ๊ฐ’์„ ๊บผ๋‚ด์•ผ ํ•˜์ง€๋งŒ ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ์ž์ฃผ ์‚ฌ์šฉํ•˜๊ธฐ ๋–„๋ฌธ์— ํƒ€์ž„๋ฆฌํ”„์—์„œ ์ง์ ‘ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

 

๋ธ”๋กœ๊ทธ์˜ ์ •๋ณด

Study Repository

rlaehddnd0422

ํ™œ๋™ํ•˜๊ธฐ