[TIL] #7. 스프링 MVC - 웹 페이지 만들기 ②

kiteB·2021년 9월 18일
0

TIL-Spring3

목록 보기
7/7
post-thumbnail
post-custom-banner

상품 상세

상품 상세 컨트롤러와 뷰를 개발하자!

BasicItemController - 상품 상세 추가

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

PathVariable로 넘어온 상품 ID로 상품을 조회하고, 모델에 추가한 뒤, 뷰 템플릿을 호출한다.


item.html

🔗 코드 확인하기

실행 결과


상품 등록 폼

BasicItemController - 상품 등록 추가

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

@PostMapping("/add")
public String save() {
    return "xxx";
}

addForm.html

🔗 코드 확인하기

속성 변경 - th:action

  • HTML form에서 action에 값이 없으면 현재 URL에 데이터를 전송한다.
  • 상품 등록 폼의 URL과 실제 상품 등록을 처리하는 URL을 똑같이 맞추고 HTTP 메서드로 두 기능을
    구분
    한다.
    • 상품 등록 폼: GET /basic/items/add
    • 상품 등록 처리: POST /basic/items/add

→ 이렇게 하면 하나의 URL로 등록 폼과, 등록 처리를 깔끔하게 처리할 수 있다.

실행 결과


상품 등록 처리 - @ModelAttribute

상품 등록 폼에서 전달된 데이터로 실제 상품을 등록 처리 해보자!

POST - HTML Form

  • content-type: application/x-www-form-urlencoded
  • 메시지 바디에 쿼리 파라미터 형식으로 전달

요청 파라미터 형식을 처리해야 하므로 @RequestParam을 사용하자!


addItemV1

@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 String itemName: itemName 요청 파라미터 데이터를 해당 변수에 받는다.
  • Item 객체를 생성하고, itemRepository를 통해서 저장한다.
  • 저장된 item을 모델에 담아서 뷰에 전달한다.

😣 간단하게 아이템을 저장하는 로직이지만 코드가 너무 길다!


addItemV2 - ModelAttribute

@PostMapping("/add")
public String addItemV2(@ModelAttribute("item") Item item, Model model) {
    itemRepository.save(item);
    //model.addAttribute("item", item); //자동 추가, 생략 가능
    return "basic/item";
}

@ModelAttribute

  • 요청 파라미터 처리
    : @ModelAttributeItem 객체를 생성하고, 요청 파라미터의 값을 프로퍼티 접근법(setXxx)으로 입력해준다.
  • Model 추가
    • Model@ModelAttribute로 지정한 객체를 자동으로 넣어준다. (주석 처리한, model.addAttribute 코드가 필요 없다는 뜻이다!)
    • 모델에 데이터를 담을 때 이름은 필요하다.
      이름은 @ModelAttribute에 지정한 name(value) 속성을 사용한다.

addItemV3 - ModelAttribute 이름 생략

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

@ModelAttribute 이름을 생략할 수 있다!
→ 생략 시 모델에 저장될 때, 클래스의 첫 글자만 소문자로 변경해서 등록한다.


addItemV4 - ModelAttribute 전체 생략

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

이름 뿐만 아니라 @ModelAttribute 애노테이션까지 생략 가능하다.
대상 객체는 모델에 자동으로 등록된다.


상품 수정

BasicController - 상품 수정 폼 컨트롤러 추가

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

수정에 필요한 정보를 조회하고, 수정용 폼 뷰를 호출한다.


editForm.html - 상품 수정 폼 뷰

🔗 코드 확인하기


BasicController - 상품 수정 개발 추가

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

상품 수정은 상품 등록과 비슷하게, 메소드에 따라 다른 동작을 한다.

  • GET items/{itemId}/edit: 상품 수정 폼
  • POST items/{itemId}/edit: 상품 수정 처리

✔ 리다이렉트

상품 수정은 마지막에 뷰 템플릿을 호출하는 대신에 상품 상세 화면으로 이동하도록 리다이렉트를 호출한다.

스프링은 redirect:/...으로 편리하게 리다이렉트를 지원한다.


PRG Post/Redirect/Get

사실 지금까지 개발해왔던 상품 등록 처리 컨트롤러는 심각한 문제가 있다!


문제 상황

상품 등록을 완료하고 새로고침 버튼을 누르면,

누르는 횟수만큼 상품이 계속 중복 등록된다!

웹 브라우저의 새로고침마지막에 서버에 전송한 데이터를 다시 전송한다.
그래서 상품을 등록하고 새로고침을 하면, 마지막에 전송한 POST /add + 상품 데이터를 서버로 다시 전송한다.

이로 인해 내용이 같고, ID만 다른 상품 데이터가 계속 쌓이게 된다!


해결 방법 - POST, Redirect GET

  • POST 요청으로 상품 저장을 한 뒤, 상품 상세화면으로 리다이렉트를 호출해주면 된다! (2번 처럼)
  • 웹 브라우저는 리다이렉트의 영향으로 상품 저장 후 상품 상세 화면으로 이동하게 된다.

BasicItemController - PRG 적용 코드 추가

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


이제 아무리 새로고침을 눌러도 딱! 하나만 저장되는 것을 확인할 수 있다!

📌 주의

"redirect:/basic/items/" + item.getId() redirect에서 + 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를 추가해보자.
뷰 템플릿에서 이 값이 있으면, "저장 완료" 라는 메시지를 출력해주자!

RedirectAttributes

RedirectAttributes를 사용하면 URL 인코딩도 해주고, pathVariable, 쿼리 파라미터까지 처리해준다.

  • redirect: /basic/items/{itemId}
    • pathVariable 바인딩: {itemId}
    • 나머지는 쿼리 파라미터로 처리: ?status=true

뷰 템플릿 메시지 추가

<h2 th:if="${param.status}" th:text="'저장 완료!'"></h2>
  • th:if: 해당 조건이 참이면 실행
  • ${param.status}: 타임리프에서 쿼리 파라미터를 편리하게 조회하는 기능

실행 결과


흑흑,,, 드디어 스프링 MVC 1편 끝냈다,,,

profile
🚧 https://coji.tistory.com/ 🏠
post-custom-banner

0개의 댓글