인프런 커뮤니티 질문&답변

blackhole124님의 프로필 이미지
blackhole124

작성한 질문수

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술

상품 등록 처리 - @ModelAttribute

addForm 페이지에 들어가면 오류가 발생합니다.

작성

·

633

·

수정됨

0

학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.

1. 강의 내용과 관련된 질문을 남겨주세요.
2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.
(자주 하는 질문 링크: https://bit.ly/3fX6ygx)
3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.
(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)

질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.
=========================================
[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? (예/아니오)
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)
3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)

[질문 내용]

아래 코드는 addForm.html 코드이고

<!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'"
                        th:onclick="|location.href='@{/basic/items}'|"
                        type="button">취소</button>
                </div>
            </div>
    </form>
    th:onclick="|location.href='@{/basic/items}'|" type="button">취소</button>
</div> <!-- /container -->
</body>
</html>
package hello.itemservice.web.basic;


import hello.itemservice.domain.item.Item;
import hello.itemservice.domain.item.ItemRepository;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Controller
@RequestMapping("/basic/items")
/**
 *  basic/items로 get 방식으로 오면 @GetMapiing 메소드가 실행이 된다.
 */
@RequiredArgsConstructor
/**
 * @RequiredArgsConstructor을 쓰면
 * final이 붙은 멤버에게는
 *     @Autowired
 *     public BasicItemController(ItemRepository itemRepository) {
 *         this.itemRepository = itemRepository;
 *     }라는 생성자를 자동으로 만들어준다.
 */
public class BasicItemController {

    /**
     * 1.BasicItempContorller가 스프링 빈에 등록이 되면서
     * 2.생성자 주입으로 스프링 빈으로 등록되어 있는 ItempRpository가 스프링 빈에서 주입이 된다.
     *
     * 1.Spring 컨테이너 초기화: 애플리케이션이 시작될 때 Spring 컨테이너가 초기화되고 구성 파일을 로드합니다.
     *      이 때 @Autowired 어노테이션을 확인하고 의존성 주입을 준비합니다.
     * 2.빈 객체 생성: Spring은 컨테이너에서 관리하는 빈(bean) 객체를 생성합니다.
     *   빈 객체는 @Component, @Service, @Repository, @Controller 등과 같은 어노테이션으로 표시된 클래스들을 기반으로 생성됩니다.
     * 3.의존성 주입: @Autowired 어노테이션이 적용된 생성자, 필드 또는 메서드를 확인하고 해당 의존성을 주입합니다.
     *   이때, BasicItemController 클래스에 있는 @Autowired 어노테이션이 적용된 생성자가 호출되면서 ItemRepository의 인스턴스가 주입됩니다.
     *   @Autowired란 스프링 컨테이너에 등록한 빈에게 의존관계주입이 필요할 때,
     *   DI(의존성 주입)을 도와주는 어노테이션이다. 스프링 컨테이너에 빈들을 모두 등록한 후에, 의존성 주입 단계가 이루어진다.
     * 4.애플리케이션 실행: 모든 초기화 작업이 완료되면 Spring은 애플리케이션을 실행합니다.
     */

    private final ItemRepository itemRepository;
    @GetMapping
    public String items(Model model){
        List<Item> items = itemRepository.findAll();
        model.addAttribute("items",items);
        return "basic/items"; // Return 되는 뷰 위치.
    }

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

    }

    /**
     * 실제로 데이터를 넣는 것이 아닌 form만 보여준다.
     *
     * form을 열때는 get
     * 실제 저장을 할 떈 post 사용
     * URL을 똑같게 지정한다. 이떄, 같은 URL로 오더라도 Get이나 post에 따라 addForm을 실행하거나 save를 실행한다.
     */
    @GetMapping("/add")
    public String addForm(){
        return "basic/addForm";
    }

    /**
     *
     * addForm.html form에 있는 name과 변수명을 동일하게 해야한다.
     * 이 코드에서 @ModelAttribute 가 html 의 name 속성과 Item class 를 매칭시켜주고
     * 그래서 itemRepository에 저장 로직에서 사용된다는 거까지는 이해했습니다
     */
   // @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){
        itemRepository.save(item);
      //  model.addAttribute("item",item);
        return "basic/item";
    }

    //@PostMapping("/add")
    public String addItemV3(@ModelAttribute Item item){
        itemRepository.save(item);
        //  model.addAttribute("item",item);
        return "basic/item";
    }

//    @PostMapping("/add")
    public String addItemV4(Item item){
        itemRepository.save(item);
        return "basic/addForm";
    }

    /**
     * PRG - Post/Redirect/Get
     */
    @PostMapping("/add")
    public String addItemV5(Item item) {
        itemRepository.save(item);
        return "redirect:/basic/items/" + item.getId();
    }
    @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); // @ModelAttribute Item item라고 작성을 하면 item객체를 가져옴
        return "redirect:/basic/items/{itemId}";  // 이렇게 리다이렉트로하면 PathVariable에 있는 것을 사용할 수 있음.
        //리다이렉션 부분 상품 수정 7분부터 다시 들어보기 이해안됨
        /**
         * 상품명을 itemD로 변경하면 edit이 호출되고 http 상태가 302가 된다
         * 이때, location은 items/2로 되는데 이것은 eidt의 결과가 리다이렉트라서 웹 브라우저가 2번으로 실제 item 2번으로 간다.
         */
    }

    /**
     *  테스트용 데이터 추가
     */

    @PostConstruct
    public void init(){
        itemRepository.save(new Item("itemA",1000,20));
        itemRepository.save(new Item("itemB",2000,40));
    }

}

이것은 BasicItemController 코드입니다.

스크린샷 2023-09-18 오후 11.33.52.png스크린샷 2023-09-18 오후 11.33.57.png

 

상품 등록 버튼을 누르면 위와 같은 오류가 발생합니다.

그리고2023-09-18T23:32:41.550+09:00 ERROR 29201 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.thymeleaf.exceptions.TemplateProcessingException: Could not parse as expression: "/basic/items/add" (template: "basic/addForm" - line 17, col 30)] with root cause

 

라는 오류가 발생합니다.

아무리 봐도 오류를 따라가서 수정을 해도 무엇이 문제인지 모르겠습니다.

답변 1

0

안녕하세요. blackhoild124님, 공식 서포터즈 코즈위버입니다.

addForm.html 파일에 아래와 같은 부분에 오탈자가 있는것 같습니다.

th:onclick="|location.href='@{/basic/items}'|" type="button">취소</button>

이 부분이 강의코드와 일치하는지 확인해주세요 :)

감사합니다.

 

blackhole124님의 프로필 이미지
blackhole124

작성한 질문수

질문하기