inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

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

상품 등록 처리 - @ModelAttribute

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

754

blackhole124

작성한 질문수 60

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

 

라는 오류가 발생합니다.

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

spring mvc

답변 1

0

나무늘보

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

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

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

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

감사합니다.

 

servlet과 container에 대한 질문입니다

0

29

1

api를 어느 컨트롤러에 작성해야하는지는 어떤 기준으로 해야하나요?

0

66

1

jsp 의존성 수정 요청

0

82

2

요즘 웹 서버가 주로 사용되는 이유는 SPA 구조 때문일까요 ?

0

145

1

save() 메서드 문의

0

67

1

절대 경로로 templates/basic 하위 파일 열면 css 적용 안되는 현상

0

102

1

request-body-json

0

84

2

MVC 패턴의 적용 단위

0

97

1

RequestMapping을 이용한 핸들러, 어댑터

0

120

2

save 후 결과화면

0

91

2

jsp를 이용한 view

0

98

1

application.properties에 debug 추가해도 결과가 똑같습니다.

0

181

1

수업 코드 제공 관련 문의

0

98

2

RequestMappingHandlerAdapter의 Controller 호출 과정

0

102

3

파일 오픈 시

0

70

1

스프링 배치 관련

0

78

1

@RequestParam의 defaultValue가 blank 값도 처리하는 지 여부

0

114

1

postman으로 /request-body-json-v1 호출시 500 error

0

96

1

프론트엔드와 백엔드의 mvc, rest api에 대한 질문

0

82

1

모델의 역할과 계층 분리에 대한 이해 차이 + 추가질문

0

113

1

console log 출력 관련 질문입니다.

0

75

1

애플리케이션이 실행 되지 않습니다 ㅠㅠㅠ

0

140

1

html 변경하는 부분 적용 문제

0

103

1

한글 깨짐

0

77

2