일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 다이나믹 프로그래밍
- 2638
- HTTP API
- 동적 프로그래밍
- 맛집
- dp
- 스프링 MVC
- 1로 만들기
- Servlet
- 백준
- 알고리즘
- 프로그래머스
- 호유동
- 문자열 압축
- 포두부 보쌈
- 2589
- 양꼬치
- 고모네 콩탕
- 완도산회
- BFS
- 스프링
- 2839
- 투어
- Spring
- mvc
- 맛집 투어
- 서블릿
- 2020 KAKAO BLIND
- 설탕 배달
- 쓰레드 풀
- Today
- Total
프로그래밍 공방
[Spring] 7. 스프링 MVC - Thymeleaf, @ModelAttribute, PRG 본문
Thymeleaf
타임리프 사용 선언
<html xmlns:th="http://www.thymeleaf.org">
속성 변경
<link href="../css/bootstrap.min.css"
th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
타임리프를 사용하여 기존 속성의 값을 변경할 때는 위와 같이 th:를 붙인 해당 속성을 하나 더 생성해주면 된다
* HTML을 그대로 볼 때는 기존 속성이 사용되고(th: 는 HTML이 모르는 문법이므로 무시된다), 뷰 템플릿을 거치면 th: 의 값이 기존 속성을 대체하면서 동적으로 변경할 수 있게 된다
* 순수 HTML을 그대로 유지하면서 뷰 템플릿도 사용할 수 있는 타임리프의 특징을 네츄럴 템플릿 (natural templates)이라 한다
* 뷰 템플릿 영역 : /resources/templates
변수 표현식 - ${ ... }
<td th:text="${item.price}">10000</td>
Model에 포함된 값이나, 타임리프 변수로 선언한 값을 조회할 수 있다(프로퍼티 접근법을 사용 / item.getPrice())
URL 링크 표현식 - @{ ... }
<td>
<a href="item.html"
th:href="@{/basic/items/{itemId}(itemId=${item.id})}"
th:text="${item.id}">회원id</a>
</td>
<td>
<a href="item.html"
th:href="@{|/basic/items/${item.id}|}"
th:text="${item.itemName}">상품명</a>
</td>
타임리프는 URL 링크를 사용하는 경우 @{ ... } 를 사용한다
* URL 링크 표현식을 사용하면 서블릿 컨텍스트를 자동으로 포함한다.
th:href="@{/basic/items/{itemId}(itemId=${item.id})}"
- URL 링크 표현식을 사용하면 경로를 템플릿처럼 편리하게 사용할 수 있다.
* 경로 변수( {itemId} ) 뿐만 아니라 쿼리 파라미터도 생성한다.
예) th:href="@{/basic/items/{itemId}(itemId=${item.id}, query='test')}"
생성 링크: http://localhost:8080/basic/items/1?query=test
th:href="@{|/basic/items/${item.id}|}"
- 아래에서 설명할 리터럴 대체 문법을 활용해서 위와 같이 간단히 사용할 수도 있다
리터럴 대체 - | ... |
타임리프에서는 아래와 같이 문자와 표현식 등은 분리되어 있기 때문에 더해서 사용해야 한다.
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
th:onclick="'location.href=' + '\'' + @{/basic/items/add} + '\''"
리터럴 대체 문법을 사용하면 편리하게 사용할 수 있다
<span th:text="|Welcome to our application, ${user.name}!|">
th:onclick="|location.href='@{/basic/items/add}'|"
반복 출력 - th:each
<tr th:each="item : ${items}">
<td><a href="item.html" th:href="@{/basic/items/{itemId}(itemId=${item.id})}" th:text="${item.id}">회원id</a></td>
<td><a href="item.html" th:href="@{|/basic/items/${item.id}|}" th:text="${item.itemName}">상품명</a></td>
<td th:text="${item.price}">10000</td>
<td th:text="${item.quantity}">10</td>
</tr>
th:each를 사용해서 반복을 표현할 수 있다
속성 변경 - th:action
HTML form에서 action에 값이 없으면 현재 URL에 데이터를 전송
상품 등록 폼의 URL과 실제 상품 등록을 처리하는 URL을 똑같이 맞추고 HTTP 메서드로 두 기능을 구분하면 하나의 URL로 등록 폼과, 등록 처리를 깔끔하게 처리할 수 있다
- 상품 등록 폼: GET /basic/items/add
- 상품 등록 처리: POST /basic/items/add
@ModelAttribute
단계별 변화를 통해 @ModelAttribute에 대해 알아보자
V1
@PostMapping("/add")
public String addItemV1(@RequestParam String itemName,
@RequestParam int price,
@RequestParam Integer quantity,
Model model) {
/* ... */
return "basic/item";
}
- @RequestParam을 통해 요청 파라미터 데이터를 변수로 받고 함수 내에서 필요한 객체를 생성하고 로직을 수행한다
V2
@PostMapping("/add")
public String addItemV2(@ModelAttribute("item") Item item, Model model) {
/* ... */
return "basic/item";
}
- @ModelAttribute는 Item 객체를 생성하고 요청 파라미터 값을 프로퍼티 입력값을 사용해서 입력해준다
@ModelAttribute - Model 추가
- @ModelAttribute는 모델(Model)에 @ModelAttribute로 지정한 객체를 자동으로 넣어준다
- Model에 담는 데이터의 이름은 @ModelAttribute에 지정한 name 속성을 사용한다
- @ModelAttribute("hello") Item item 이름을 hello 로 지정
- model.addAttribute("hello", item); 모델에 hello 이름으로 저장
V3
@PostMapping("/add")
public String addItemV3(@ModelAttribute Item item) {
/* ... */
return "basic/item";
}
- @ModelAttribute의 이름을 생략한다면 Model에 저장할 때 클래스 명의 첫글자만 소문자로 변경해서 등록한다
- @ModelAttribute Item item -> item 으로 등록
V4
@PostMapping("/add")
public String addItemV4(Item item) {
/* ... */
return "basic/item";
}
- @ModelAttribute 자체도 생략가능하다 (모델에도 동일하게 자동 등록된다)
Redirect / 리다이렉트
스프링은 redirect:/... 으로 리다이렉트를 지원한다.
@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @ModelAttribute Item item) {
/* ... */
return "redirect:/basic/items/{itemId}";
}
* 컨트롤러에 매핑된 @PathVariable 의 값은 redirect 에도 사용 할 수 있다
PRG Post/Redirect/Get
Post 관련 처리를 할 때 주의해야 하는 문제가 있다
POST 등록 후 새로고침
* 웹 브라우저의 새로 고침은 마지막에 서버에 전송한 데이터를 다시 전송한다
상품 등록 폼에서 데이터를 입력하고 저장을 선택하면 POST /add + 상품 데이터를 서버로 전송한다
이 상태에서 새로 고침을 선택하면 마지막에 전송한 POST /add + 상품 데이터를 서버로 다시 전송하게
된다. 그래서 내용은 같고, ID만 다른 상품 데이터가 계속 쌓이게 된다
POST, Redirect GET
새로 고침 문제를 해결하려면 상품 저장 후에 뷰 템플릿으로 이동하는 것이 아니라,
상품 상세 화면으로 리다이렉트를 호출해주면 된다
웹 브라우저는 리다이렉트의 영향으로 상품 저장 후에 실제 상품 상세 화면으로 다시 이동한다
따라서 마지막에 호출한 내용이 상품 상세 화면인 GET /items/{id} 가 되는 것이다
이후 새로고침을 해도 상품 상세 화면으로 이동하게 되므로 새로 고침 문제를 해결할 수 있다
* 이런 문제 해결 방식을 PRG Post/Redirect/Get 라 한다
RedirectAttributes
@PostMapping("/add")
public String addItem(Item item, RedirectAttributes redirectAttributes) {
/* ... */
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/basic/items/{itemId}";
}
RedirectAttributes 를 사용하면 URL 인코딩도 해주고, pathVarible, 쿼리 파라미터까지 처리해준다
- redirect:/basic/items/{itemId}
- pathVariable 바인딩: {itemId}
- 나머지는 쿼리 파라미터로 처리: ?status=true
이런 RedirectAttributes는 아래와 같이 사용할 수 있다
<div class="container">
<h2 th:if="${param.status}" th:text="'저장 완료!'"></h2>
</div>
- th:if : 해당 조건이 참이면 실행
- ${param.status} : 타임리프에서 쿼리 파라미터를 편리하게 조회하는 기능
- 타임리프에서 지원하는 쿼리 파라미터 사용 방식
'개발 > 스프링' 카테고리의 다른 글
[Spring] 6-2. 스프링 MVC - 기본 기능 (0) | 2021.11.02 |
---|---|
[Spring] 6-1. 스프링 MVC - Logging (0) | 2021.10.31 |
[Spring] 5. 스프링 MVC - 구조 이해 (0) | 2021.10.27 |
[Spring] 4. 프레임워크 만들기 (0) | 2021.10.25 |
[Spring] 3. Servlet, JSP, MVC 패턴 (0) | 2021.10.18 |