728x90
반응형
SMALL

참고자료:

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 강의 | 김영한 - 인프런

김영한 | 웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습

www.inflearn.com

이제 기능적으로 알아두면 좋을 것들 몇가지를 작성해보고자 한다. 

 

@ModelAttribute

Part.2에서 잠시 다뤘던 적이 있는 이 @ModelAttribute는 이런 기능들을 대신해준다.

다음 코드를 보자.

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

 

이 코드에 대해 조금 설명을 하자면, 어떤 상품 등록을 하는 폼이 있고 그 폼에서 상품 등록 버튼을 클릭하면 호출되는 @PostMapping이다. 그럼 상품 등록을 폼으로 한다고 하면 폼으로부터 전달되는 데이터가 있을 것인데 그 데이터가 저 @ModelAttribute로 담기게 된다.

그리고 타입은 Item 이라는 클래스인데 다음과 같이 생겼다.

Item

package hello.itemservice.domain.item;

import lombok.Data;

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

 

그래서 폼으로부터 들어오는 데이터가 이 클래스의 필드로 다 만족을 하면 스프링 MVC는 자동으로 폼으로 들어오는 데이터를 저렇게 바인딩 할 수 있다. 그래서 정확히 저 코드는 사실 이렇게 생긴것이다.

@PostMapping("/add")
public String save(@RequestParam String itemName,
                   @RequestParam Integer price,
                   @RequestParam Integer quantity,
                   Model model) {
    // @ModelAttribute가 대신 해주는 작업                   
    Item item = new Item();
    item.setItemName(itemName);
    item.setPrice(price);
    item.setQuantity(quantity);
    model.addAttribute("item", item);
    // @ModelAttribute가 대신 해주는 작업 끝

    itemRepository.save(item);
    return "basic/item";
}

 

"어? Item 객체를 만들어서 데이터를 넣어주는 것까진 알겠는데 model.addAttribute("item", item); 까지 해준다고?!" 그렇다. 저 @ModelAttributeModel 객체에 데이터를 담아주는 것까지 해준다. 그리고 그때 key값은 클래스의 앞글자만 소문자로 바꾼 형태가 된다. (Itemitem)

 

Redirect

이번엔 리다이렉트를 하는 방법이다. 간단하다.

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

 

저렇게 "redirect:/redirect할 경로" 를 입력하면 된다. 그리고 리다이렉트 할 경로에 PathVariable이 있는 경우 코드처럼 문자열로 {pathVariable}를 입력하면 된다. 그럼 스프링이 알아서 이 메서드의 @PathVariable에 있는 값을 매핑시켜준다.

 

당연히 이렇게도 가능하다. 오히려 @PathVariable을 받지 않는 메서드에서 itemId가 있어야 하는 경로로 리다이렉트 할 땐 아래 코드처럼만 해야한다.

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

 

근데 사실 좀 짜치는것도 있고, 더 큰 문제는 URL은 모든게 다 문자열로 이루어져 있다. 그래서 인코딩이 필수다. 숫자 같은 건 괜찮은데 한글이 만약 들어간다? 바로 깨진다. 그래서 인코딩이 필수이고 그 방법이 RedirectAttribute라는 게 있다.

@PostMapping("/add")
public String save(@ModelAttribute Item item, RedirectAttributes redirectAttributes) {
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);

    return "redirect:/basic/items/{itemId}";
}

이렇게 파라미터로 RedirectAttributes를 받으면 이 녀석을 사용할 수가 있는데, addAttribute()로 원하는 key/value를 넣으면 그 key를 가지고 {PathVariable}을 사용할 수가 있다. 

 

그리고 "status"라는 키도 있는데 이렇게 addAttribute()key/value를 저장하고 PathVariable로 사용하지 않는건 쿼리 파라미터로 들어간다. 그리고 이 키는 저장이 잘 됐다면 리다이렉트된 화면에서 뭔가 잘 저장됐다는 표시를 보여주고 싶어서 플래그를 사용했다고 생각하면 된다. 그리고 그 플래그를 Thymeleaf랑 같이 사용할 때 이렇게 param라는 키로 받을수가 있다.

<h2 th:if="${param.status}" th:text="'저장 완료'"></h2>

 

이런 방식이 훨씬 더 깔끔하고 인코딩도 다 해주기 때문에 더 좋은 접근방법이다. 이렇게 사용하자.

 

 

PRG - Post/Redirect/Get

이건 실무에서도 자주 사용되는 방식인데, 폼을 통해 POST 요청을 하고 보여지는 화면에서 사용자가 새로 고침을 누르면 POST 요청이 계속 들어간다. 그런 경우에 POST 요청이 계속 들어오면 만약 그게 상품 저장 기능이었다면 새로 고침한만큼 상품 저장이 되는 문제가 발생한다. 그 것을 방지하기 위해 폼을 통해 POST 요청을 처리하는 컨트롤러에서는 그 요청의 반환으로 Redirect를 해서 GET으로 최종 목적지를 변경해줘야 한다.

@PostMapping("/add")
public String save(@ModelAttribute Item item, RedirectAttributes redirectAttributes) {
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);

    return "redirect:/basic/items/{itemId}";
}

 

그래서 위 코드도 POST로 요청이 들어온 상품 저장 기능에 반환으로 리다이렉트를 통해 저장된 상품의 상세 목록으로 페이지를 이동시킨다. 그래야 사용자는 저장한 후 보여지는 화면에서 새로고침을 눌러도 POST 요청이 계속 발생하지 않는다. 그러니까 새로고침은 가장 마지막에 한 행위를 다시 하는것이다. 그래서 새로고침을 누르더라도 POST 요청이 다시 일어나지 않도록 리다이렉트로 마지막에 요청한 행위는 그저 상품 상세 화면을 보고 있는 GET 요청으로 바꿔줘야 한다. 

 

 

728x90
반응형
LIST

+ Recent posts