@ModelAttribute와 PRG 패턴으로 폼 요청 처리하기
@RequestParam에서 @ModelAttribute로 폼 처리 코드를 줄이고, PRG와 RedirectAttributes로 중복 등록을 막는다.
시리즈 · Spring5 / 14
- SOLID 원칙과 OCP·DIP를 지키는 방법
- 의존성 주입(DI)과 스프링 컨테이너
- 쓰레드 풀과 싱글톤 서블릿의 동시성 문제
- FrontController 패턴과 핸들러 어댑터로 스프링 MVC 구조 이해하기
- @ModelAttribute와 PRG 패턴으로 폼 요청 처리하기
- System.out 대신 SLF4J로 로그 남기기
- 세션 직접 만들어 보기와 HttpSession
- 스프링 인터셉터와 예외 발생 시 오류 페이지 흐름
- 서블릿 Part와 MultipartFile로 파일 업로드 처리하기
- JDBC로 CRUD 구현하기와 커넥션 풀, DataSource
- JDBC 트랜잭션에서 스프링 트랜잭션 동기화와 트랜잭션 AOP까지
- JPA 변경 감지(dirty checking)와 병합(merge)
- 서블릿 컨테이너 초기화와 스프링 컨테이너 등록 과정
- 싱글톤 빈의 동시성 문제와 ThreadLocal
상품 등록 폼을 처리하는 컨트롤러를 단계적으로 개선해 본다.
@RequestParam
@PostMapping("/add")
public String saveItemV1(@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/addForm";
}
파라미터를 하나씩 @RequestParam으로 받아 객체에 직접 set 하고, model.addAttribute로 모델에 등록하는 과정까지 모두 작성해야 한다.
@ModelAttribute
@PostMapping("/add")
public String saveItemV2(@ModelAttribute("item") Item item) {
itemRepository.save(item);
return "basic/addForm";
}
@ModelAttribute는 두 가지 일을 해 준다.
- 요청 파라미터를
Item객체의 프로퍼티에 바인딩한다. - 그 객체를 지정한 이름(
"item")으로 모델에 등록한다.
이름은 생략할 수 있다. 생략하면 클래스명의 첫 글자를 소문자로 바꾼 이름(Item → item)으로 모델에 등록된다.
@PostMapping("/add")
public String saveItemV3(@ModelAttribute Item item) {
itemRepository.save(item);
return "basic/addForm";
}
@ModelAttribute 자체도 생략할 수 있다.
@PostMapping("/add")
public String saveItemV4(Item item) {
itemRepository.save(item);
return "basic/addForm";
}
PRG (Post/Redirect/Get)
POST로 상품을 등록한 뒤 뷰를 바로 반환하면, 브라우저에서 새로고침할 때 마지막 요청인 POST가 그대로 다시 전송되어 상품이 중복 등록된다.

POST 처리 후 GET 요청으로 리다이렉트하면 새로고침을 해도 GET만 다시 요청되므로 문제가 해결된다.
@PostMapping("/add")
public String addItemV5(Item item) {
itemRepository.save(item);
return "redirect:/basic/items/" + item.getId();
}
다만 이렇게 URL에 값을 직접 더하면 URL 인코딩이 되지 않는다. RedirectAttributes를 사용하는 편이 좋다.
@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}";
}
URL 템플릿({itemId})에 사용된 속성은 경로에 치환되고, 사용되지 않은 나머지 속성은 쿼리 파라미터로 붙는다.
http://localhost:8080/basic/items/3?status=true
Thymeleaf에서는 ${param.status}로 이 값에 접근할 수 있다.
참고
- 김영한, 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (인프런)