여러 단계에 걸쳐서 입력된 데이터를 HttpSession에 옮겨담아 데이터가 유지되도록 해주는 어노테이션
Model 객체에 저장되는 데이터를 HttpSession에 저장시키는 어노테이션
컨트롤러 클래스에 @SessionAttributes 어노테이션이 있으면
해당 컨트롤러의 요청핸들러 메서드에서 Model객체에 저장되는 데이터를 HttpSession 객체의 속성으로 저장시켜서 해당 요청이 완료된 후에도 Model객체에 저장한 데이터가 유지되도록 한다.
다른 요청핸들러 메서드와 Model객체에 저장된 데이터를 공유하기 위해서다.
Model객체에 저장되는 모든 데이터가 HttpSession에 저장되는 것이 아니라,
@SessionAttributes 어노테이션에서 지정한 이름과 동일한 이름으로 Model 객체에 저장되는 데이터만 대상이 된다.
@Getter
@Setter
@ToString
public class OrderForm {
// 1단계에서 입력되는 값(orderform.jsp)
private int productNo;
private String name;
private int price;
private int amount;
private int totalPrice;
// 2단계에서 입력되는 값(payform.jsp)
private String payType;
private String cardno;
private int months;
private int payAmount;
}
@Controller
@RequestMapping("/order")
@RequiredArgsConstructor
@SessionAttributes({"orderForm"}) // ==> 해당 Controller 안에서 orderForm이라는 이름으로
public class OrderController { // Model에 담기는 게 있으면, Session의 속성으로 옮겨담는 역할
private final ProductService productService;
@GetMapping("/step1")
public String step1(@RequestParam("no") int productNo, Model model) {
// 1. 주문폼 화면에 출력할 상품정보를 조회하고, Model객체에 저장한다.
Product product = productService.getProduct(productNo);
model.addAttribute("product", product);
// 2. 여러 단계로 구분된 입력작업에서 입력되는 데이터를 저장할 OrderForm객체를 생성하고,
// Model객체에 저장한다.
// "orderForm"이라는 이름으로 Model객체에 저장되는 데이터는
// @SessionAttributes("orderForm") 설정 때문에 OrderForm객체가 HttpSession에 자동으로 저장된다.
model.addAttribute("orderForm", new OrderForm());
return "order/orderform";
}
// 2단계, 3단계에서는 1단계에서 전달받은 Model의 데이터가 변경되지 않고 유지된다.
@PostMapping("/step2")
public String step2(OrderForm orderForm) {
return "order/payform";
}
@PostMapping("/step3")
public String step3(OrderForm orderForm) {
return "order/completed";
}
}
${orderForm.프로퍼티명 }
으로 출력 가능<table class="table">
<thead>
<tr>
<th>상품이름</th>
<td>${orderForm.name }</td>
<th>상품가격</th>
<td><fmt:formatNumber value="${orderForm.price }" /> 원</td>
</tr>
<tr>
<th>구매수량</th>
<td><fmt:formatNumber value="${orderForm.amount }" /> 개</td>
<th>총 구매가격</th>
<td><fmt:formatNumber value="${orderForm.totalPrice }" /> 원</td>
</tr>
</thead>
</table>
클라이언트로부터의 요청 데이터를 받아와 컨트롤러 메서드의 매개변수에 바인딩하는 어노테이션
(주로 HTML 폼으로부터 받은 데이터를 모델에 바인딩하거나, 공통적인 데이터를 모델에 추가하는 데 사용됨)
메서드에 @ModelAttribute 어노테이션을 지정하면,
Spring mvc는 요청핸들러 메서드를 실행하기 전에 @ModelAttribute 어노테이션이 지정된 메서드를 먼저 실행해서 그 메서드가 반환하는 값을 Model 객체에 저장시킨다.
@ModelAttribute 어노테이션을 ControllerAdvice에 등록해놓으면 모든 컨트롤러에서 사용 가능하고, Controller에서 정의하면 해당 컨트롤러에서만 사용 가능하다.
따라서, 많은 요청핸들러 메서드가 실행된 다음에 내부이동하는 JSP에서 특정한 값을 표현하는 (공통적인)부분이 있으면,
각각의 요청핸들러 메서드에서 그 정보를 조회해서 Model객체에 저장하지 말고
@ModelAttribute 어노테이션이 지정된 메서드에서 정보를 조회하고 반환하도록 하자.
public String sample(@ModelAttribute("sampleForm") SampleForm form) { ... }
@ModelAttribute("categories")
public List<Category> categories() {
List<Category> categories = categoryService.getAllCategories();
return categories;
}
@ControllerAdvice
@RequiredArgsConstructor
public class ModelAttributeAdvice {
private final ProductService productService;
// 해당 메서드가 반환하는 값을 Model 객체에 저장시키는데,
// 그 이름이 name = "productCategories"
// (중간단계 없이 jsp에서 이 이름으로 바로 호출 가능)
@ModelAttribute(name = "productCategories")
public List<ProductCategory> productCategories() {
return productService.getAllProductCategories();
}
}
<div class="card">
<div class="card-header">상품 카테고리</div>
<div class="list-group list-group-flush">
<!-- productCategories 사용 -->
<c:forEach var="category" items="${productCategories }">
<a href="/product/list?catNo=${category.no}"
class="list-group-item list-group-item-action">
${category.name }
</a>
</c:forEach>
</div>
</div>
<div class="col-3">
<!-- include 지시어로 category.jsp 파일 삽입 -->
<%@ include file="../common/category.jsp" %>
</div>
Q. 둘다 Model에 저장하고, 지정한 변수명으로 jsp에서 출력할 수 있는 건 똑같은데 무슨 차이지?
A. 둘의 역할은 엄연히 다르다.
각각의 개념을 다시 살펴보면,
@SessionAttributes는 여러 단계에 걸쳐서 입력된 데이터를 HttpSession에 옮겨담아 데이터가 유지되도록 해주는 어노테이션이고,
@ModelAttribute는 클라이언트로부터의 요청 데이터를 받아와 컨트롤러 메서드의 매개변수에 바인딩하는 어노테이션이다.
@ModelAttribute 단독 사용 시 여러 단계로 구분된 입력작업에서 데이터 유지가 되지 않고,
@SessionAttributes는 컨트롤러 메소드의 매개변수로 전달된 요청 데이터를 처리하는 데는 사용되지 않는다.