[Spring] 7.์Šคํ”„๋ง MVC - ์›น ํŽ˜์ด์ง€ ๋งŒ๋“ค๊ธฐ

 

1. ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

ํŒŒ์ผ์ฐธ๊ณ 


2. ์š”๊ตฌ์‚ฌํ•ญ ๋ถ„์„

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

- ์ƒํ’ˆ ID

- ์ƒํ’ˆ๋ช…

- ๊ฐ€๊ฒฉ

- ์ˆ˜๋Ÿ‰

 

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

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

- ์ƒํ’ˆ ์ƒ์„ธ

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

- ์ƒํ’ˆ ์ˆ˜์ •

 


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

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

@Data
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;
  } 
}

* @Data(๋กฌ๋ณต)๋Š” ํ•ต์‹ฌ ๋„๋ฉ”์ธ ๋ชจ๋ธ์— ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ—˜ํ•จ (getter setter๋งŒ ์ถ”์ฒœ)

 

์•„์ดํ…œ ์ €์žฅ์†Œ

@Repository
public class ItemRepository {
private static final Map<Long, Item> store = new HashMap<>(); //static ์‚ฌ์šฉ private static long sequence = 0L; //static ์‚ฌ์šฉ
      
      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() {
          store.clear();
      }  
      
}

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

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

- HTML ํŽธ๋ฆฌ ๊ฐœ๋ฐœ ์œ„ํ•ด ์‚ฌ์šฉ


5. ์ƒํ’ˆ ๋ชฉ๋ก - ํƒ€์ž„๋ฆฌํ”„

BasicItemController

@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("testA", 10000, 10));
    itemRepository.save(new Item("testB", 20000, 20));
  }
}

@RequiredArgsConstructor : final ์ด ๋ถ™์€ ๋ฉค๋ฒ„๋ณ€์ˆ˜๋งŒ ์‚ฌ์šฉํ•ด์„œ ์ƒ์„ฑ์ž๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์ค€๋‹ค.

public BasicItemController(ItemRepository itemRepository) {
    this.itemRepository = itemRepository;
}

 

์ƒํ’ˆ ๋ชฉ๋ก view

<!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">
  </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>
      <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>

6. ์ƒํ’ˆ ์ƒ์„ธ

BasicItemController์— ์ถ”๊ฐ€

@GetMapping("/{itemId}")
public String item(@PathVariable Long itemId, Model model) {
  Item item = itemRepository.findById(itemId);
  model.addAttribute("item", item);
  return "basic/item";
}
 
๋ทฐ
<!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>
<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"
value="์ƒํ’ˆA" th:value="${item.itemName}" readonly> </div>
<div>
<label for="price">๊ฐ€๊ฒฉ</label>
          <input type="text" id="price" name="price" class="form-control"
  value="10000" th:value="${item.price}" readonly>
</div> <div>
<label for="quantity">์ˆ˜๋Ÿ‰</label>
          <input type="text" id="quantity" name="quantity" class="form-control"
  value="10" th:value="${item.quantity}" 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'"
th:onclick="|location.href='@{/basic/items/{itemId}/ edit(itemId=${item.id})}'|" type="button">์ƒํ’ˆ ์ˆ˜์ •</button>
          </div>
          <div class="col">
              <button class="w-100 btn btn-secondary btn-lg"
  onclick="location.href='items.html'"
th:onclick="|location.href='@{/basic/items}'|" type="button">๋ชฉ๋ก์œผ๋กœ</button>
          </div>
      </div>
  </div> <!-- /container -->
  </body>
  </html>

7. ์ƒํ’ˆ ๋“ฑ๋ก ํผ

BasicItemController์— ์ถ”๊ฐ€

@GetMapping("/add")
public String addForm() {
      return "basic/addForm";
}

๋ทฐ

<!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>
<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"
onclick="location.href='items.html'"
        </div>
    </div>
</form>
th:onclick="|location.href='@{/basic/items}'|" type="button">์ทจ์†Œ</button>
  </div> <!-- /container -->
  </body>
  </html>

 

์ƒํ’ˆ ๋“ฑ๋ก ์ฒ˜๋ฆฌ (POST - HTML form)

BasicItemController์— ์ถ”๊ฐ€

@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";
}

 

์ƒํ’ˆ ๋“ฑ๋ก ์ฒ˜๋ฆฌ  (ModelAttribute)

 /**
* @ModelAttribute("item") Item item
* model.addAttribute("item", item); ์ž๋™ ์ถ”๊ฐ€ */
  @PostMapping("/add")
  public String addItemV2(@ModelAttribute("item") Item item, Model model) {
itemRepository.save(item); //model.addAttribute("item", item); //์ž๋™ ์ถ”๊ฐ€, ์ƒ๋žต ๊ฐ€๋Šฅ
      return "basic/item";
  }

* modelattribute ์ด๋ฆ„ ์ƒ๋žต ๊ฐ€๋Šฅ


8. ์ƒํ’ˆ ์ˆ˜์ •

BasicItemController์— ์ถ”๊ฐ€

@GetMapping("/{itemId}/edit")
public String editForm(@PathVariable Long itemId, Model model) {
      Item item = itemRepository.findById(itemId);
      model.addAttribute("item", item);
      return "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"
th:value="${item.price}">
</div> <div>
<label for="quantity">์ˆ˜๋Ÿ‰</label>
            <input type="text" id="quantity" name="quantity" class="form-
control" 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})}'|"
        </div>
    </div>
</form>
type="button">์ทจ์†Œ</button>
  </div> <!-- /container -->
  </body>
  </html>

์ˆ˜์ • ๋ฐ˜์˜

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

*๋ฆฌ๋‹ค์ด๋ ‰ํŠธ : ๋ทฐ ํ…œํ”Œ๋ฆฟ ํ˜ธ์ถœํ•˜๋Š” ๋Œ€์‹  ์ƒํ’ˆ ์ƒ์„ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•˜๋„๋ก


9. PRG : POST / REDIRECT / GET

์‹ฌ๊ฐํ•œ ๋ฌธ์ œ

- ์ƒํ’ˆ ๋“ฑ๋กํ•˜๊ณ  ์›น๋ธŒ๋ผ์šฐ์ € ์ƒˆ๋กœ๊ณ ์นจ ๋ˆ„๋ฅด๋ฉด ์ค‘๋ณต ๋“ฑ๋ก๋จ

- ์ƒํ’ˆ ๋“ฑ๋ก ํ›„ ์ƒํ’ˆ ์ƒ์„ธ ๋ทฐ๋ฅผ ํ˜ธ์ถœํ•ด์„œ ๋ณด๋‚ด๋Š”๋ฐ URL์€ ๊ทธ๋Œ€๋กœ ์ƒํ’ˆ ์ €์žฅ URL์— ๋‚จ์•„์žˆ์Œ

 

์ „์ฒด ํ๋ฆ„

POST ๋“ฑ๋ก ํ›„ ์ƒˆ๋กœ๊ณ ์นจ

ํ•ด๊ฒฐํ•˜๋Š”๋ฒ•? (Post, Redirect Get)

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

 

์ปจํŠธ๋กค๋Ÿฌ ์ˆ˜์ •

/**
   * PRG - Post/Redirect/Get
   */
  @PostMapping("/add")
  public String addItemV5(Item item) {
      itemRepository.save(item);
      return "redirect:/basic/items/" + item.getId();
  }

+item.getId ๋ฐฉ์‹ ์œ„ํ—˜ (URL ์ธ์ฝ”๋”ฉ ์•ˆ๋˜๊ธฐ ๋•Œ๋ฌธ์—)

 

RedirectAttributes

- ์ €์žฅ ํ™•์ธ ๊ธฐ๋Šฅ ์ถ”๊ฐ€

/**
     * RedirectAttributes
     */
    @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}";
}
redirect:/basic/items/{itemId}
- pathVariable ๋ฐ”์ธ๋”ฉ: {itemId}
- ๋‚˜๋จธ์ง€๋Š” ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ฒ˜๋ฆฌ: ?status=true
  • ๋„ค์ด๋ฒ„ ๋ธ”๋Ÿฌ๊ทธ ๊ณต์œ ํ•˜๊ธฐ
  • ๋„ค์ด๋ฒ„ ๋ฐด๋“œ์— ๊ณต์œ ํ•˜๊ธฐ
  • ํŽ˜์ด์Šค๋ถ ๊ณต์œ ํ•˜๊ธฐ
  • ์นด์นด์˜ค์Šคํ† ๋ฆฌ ๊ณต์œ ํ•˜๊ธฐ