인프런/스프링 MVC 1편

18)타임리프 적용, @ModelAttribute의 추가 기능

backend dev 2023. 1. 29.

상품상세 메소드

BasicItemController에 상품상세를 보는 url매핑한 메소드를 만들어준다.

@GetMapping("/{itemId}") //아이템 상세보기
public String item(@PathVariable long itemId, Model model) {
    Item item = itemRepository.findById(itemId);
    model.addAttribute("item", item);
    return "basic/item";
}

상품상세 페이지를 타임리프가 동작하게끔 수정해보자.

<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="utf-8">
  <link th:href="@{/css/bootstrap.min.css}"
      href="/css/bootstrap.min.css" rel="stylesheet">
  <style>

기본적으로 추가해줄것해주고 테스트해본다.

화면은 나오지만 값에 대한 렌더링이 되지않았으므로 고정된 기본값(value)이 나오는것이다.

타임리프를 통해 value에 렌더링될때 데이터에 대한 값들이 들어가게 수정해보자

<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>

이렇게 해주면 된다. 렌더링할때 모델이 item이라는 속성이름에 item객체라는 속성값이 담겨서 넘어올테니까 

그 데이터를 이용하는것이다.

확인해보면 상품번호2번에 대한 값으로 잘바뀌어있는것을 확인할 수 있다.

상품수정  버튼 타임리프 적용

<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>

Pathvariable을 문법을 이용해서 수정하였다.

th:onclick="|location.href='@{|/basic/items/${itemId}/edit|}'|"

이렇게 사용해도 똑같이 동작한다.!  ( 리터럴 대체를 잘사용해야한다.  일반 문자열과 '내용 '을 합치기위해 제일 겉부분에 ||(리터럴대체)문법이 들어갔고  ' '안에 경로가 들어가면서 ${}라는 변수표현식을 사용하기위해 ||가 또한번 사용되었다)   

상품수정 버튼을 동작했을때 이용하는 url, 두가지방법모두 잘동작한다.

"목록으로" 버튼 타임리프 적용

<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>

잘동작함을 확인하였다


상품등록폼

@GetMapping("/add") // 상품 등록 폼으로 이동
public String addForm() {
    return "basic/addForm";
}

GetMapping을 이용하여 해당 url요청이 왔을때 상품등록폼 화면으로 이동하게끔하는 메소드 추가.

상품등록버튼 타임리프 적용

<form action="item.html" th:action="/basic/item/add" method="post">
  <div>

form태그에서 action부분을 추가해주면되는데 "/basic/item/add"로 해주면 

서버주소 + "/basic/item/add" 가 되므로 localhost:8080/basic/item/add가 될것이다. (POST방식으로)

 

그러면 저장을 위한 메소드를 추가해주자.

@PostMapping("/add") // 상품 저장메소드 , 같은 url이지만 Method가 다르므로 구분이 된다.
public String save() {
    return "basic/addForm";
}

url은 같지만 HttpMethod가 다르므로 구분되어 동작할것이다.

 

하지만 현재 GET방식으로  localhost:8080/basic/item/add이란 url를 호출해서 상품등록폼화면에 온것이고

form의 action에서 "/basic/item/add"를 설정하면 결국 같은 url이지만 method만 다른것이니까

form의 action을 다음과 같이 수정할 수 있다.

<form action="item.html" th:action method="post">

뷰가 렌더링될때 th:action으로 치환이 될텐데 아무값도 없으므로 접근한 url그 자체가 될것이다.

소스보기를 하면 비어있는것을 확인할 수 있다.

취소버튼 타임리프 적용

<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>

취소를 누르면 items 템플릿이 동작하도록 하였다.

잘동작한다.

만약에 /basic/items가 아닌 basic/items라고 입력했을경우

현재 경로 + basic/items인 url를 호출해버린다.

/로 시작해야 서버주소뒤에 붙어서 만든 url를 호출한다.

서버주소 + /basic/items

 

참고(/reasources/static 안에 들어있는 정적리소스들은 http://localhost:8080/html/items.html와 같이 주소를 입력하면 페이지를 보여주는데  http://localhost:8080/basic/items.html 이런식으로 타임리프가 적용된 html를 열어보려고 하면 화이트라벨 에러페이지를 보여준다. resources 아래 template 폴더안의 파일은 클라이언트가 직접접근 불가능하다.
war를 선택했을때 생성되는 webapp 폴더 아래 web-inf와 같이 보안폴더 개념이다.)

 

상품등록 처리 -@ModelAttribute

@RequestParam

@ModelAttribute를 사용하기전에

HTML Form은 메시지바디에 쿼리 파라미터형식으로 오므로  @Requestparam을 사용해서 처리하는것도 해보자.

form으로 넘어오는 데이터의 파라미터명은 name이라는 속성을 확인하면된다!

@PostMapping("/add") // 상품 저장메소드 , 같은 url이지만 Method가 다르므로 구분이 된다.
public String save(
    @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을 이용해 파라미터값을 가져오고, Item객체를 생성해서 값을 넣은후 저장하고

모델에 아이템객체를 넣고, 아이템상세화면을 렌더링하면 된다.

 

 

잘 동작하는것을 확인할 수 있다.

@ModelAttribute 사용

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

@ModelAttribute를 이용하여 파라미터로 들어오는 데이터를 객체로 변환할 수 있다.

그래서 Item 클래스에는 Setter가 존재해야한다.

 

잘동작하는것을 확인하였음

@ModelAttribute 사용 + 숨겨진기능

@ModelAttribute의 숨겨진기능

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

@ModelAttribute("속성이름")으로 하면 Model객체안에 addAttribute로 데이터를 넣는과정을 없애도된다.

보통 객체는 모델에 담기니까 @ModelAttribute("속성이름")으로 바로 Model담기도록 할 수 있다.

@ModelAttribute("item")Item item

이 명령어 한줄이

model.addAttribute("item", item);

를 대체한다. "item2"로 바꾸면 속성이름도 item2로 들어간다.(하지만 item2로 바꾸면 안된다. -> 뷰템플릿에서 사용하는 데이터명에 맞게 적어줘야한다)

 

잘동작하는것을 확인하였음

 

@ModelAttribute 사용 + 이름생략 + Model 생략

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

 @ModelAttribute name 생략 가능
 model.addAttribute(item); 자동 추가, 생략 가능
 생략시 model에 저장되는 name은 클래스명 첫글자만 소문자로 등록 Item -> item

 

모델에 데이터를 넣을때 필요한 속성이름이 생략가능하다. 

생락하면 클래스명의 맨앞글자만 소문자로 바뀌어서 등록한다. 

model.addAttribute("item", item);

이 자동으로 진행된다는것!

클래스명임을 유의하자

@ModelAttribute는 Model에 객체를 자동으로 넣어주기때문에, Model 객체를 파라미터로 받아올 필요가없다.

(@ModelAttribute가 스프링프레임워크에게 모델을 받아서 알아서 데이터를 등록해준다!)

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-modelattrib-method-args

 

Web on Servlet Stack

Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, "Spring Web MVC," comes from the name of its source module (spring-webmvc), but it is more commonl

docs.spring.io

설명에 보면 @ModelAttiribute를 이용하여 모델의 속성에 접근하거나 만약 없다면 객체화 할수 있다고한다.

모델에 접근해서 파라미터를 이용해서 변환시킨 객체를 속성에  추가할수 있다는것같다.

@ModelAttribute 생략

@ModelAttribute도 생략이 가능하다. 대상 객체는 모델에 자동 등록된다

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

객체를 item이라는 이름으로 모델에 담겨지게된다

댓글