핵심은 th:xxx 가 붙은 부분은 서버사이드에서 렌더링 되고, 기존 것을 대체한다. th:xxx 이 없으면 기존 html의 xxx 속성이 그대로 사용된다. HTML을 파일로 직접 열었을 때, th:xxx 가 있어도 웹 브라우저는 th: 속성을 알지 못하므로 무시한다. 따라서 HTML을 파일 보기를 유지하면서 템플릿 기능도 할 수 있다.
예시로 href=”value1”th:href=”value2” 존재 한다면, th:href값으로 href 속성값이 변경된다. 속성이 없다면 새로 생성한다.
URL 링크 표현식 - @{…}
th:href="@{/css/bootstrap.min.css}"
@{…} : 타임리프는 URL 링크를 사용하는 경우 @{…} 를 사용한다. 이것을 URL 링크 표현식이라 한다.
URL 링크 표현식을 사용하면 서블릿 컨텍스트를 자동으로 포함한다.
리터럴 대체 - |…|
타임리프에서 문자와 표현식 등은 분리되어 있기 때문에 더해서 사용해야 한다. <span th:text="'Welcome to our application, ' + ${user.name} + '!'">
다음과 같이 리터럴 대체 문법을 사용하면, 더하기 없이 편리하게 사용할 수 있다. <span th:text="|Welcome to our application, ${user.name}!|">
타임리프는 순수 HTML 파일을 웹 브라우저에서 열어도 내용을 확인할 수 있고, 서버를 통해 뷰 템플릿을 거치면 동적으로 변경된 결과를 확인할 수 있다. JSP를 생각해보면, JSP 파일은 웹 브라우저에서 그냥 열면 JSP 소스코드와 HTML이 뒤죽박죽 되어서 정상적인 확인이 불가능하다. 오직 서버를 통해서 JSP를 열어야 한다. 이렇게 순수 HTML을 그대로 유지하면서 뷰 템플릿도 사용할 수 있는 타임리프의 특징을 네츄럴 템플릿(natural templates)이라 한다.
상품 상세
BasicItemController에 추가
1 2 3 4 5 6
@GetMapping("/{itemId}") public String item(@PathVariable Long itemId, Model model){ Item item = itemRepository.findById(itemId); model.addAttribute("item", item); return"basic/item"; }
PathVariable 로 넘어온 상품ID로 상품을 조회하고, 모델에 담아둔다. 그리고 뷰 템플릿을 호출한다.
// @PostMapping("/add") public String save(@RequestParam String itemName, @RequestParam Integer price, @RequestParam Integer quantity, Model model){ Item item = new Item(itemName, price, quantity); itemRepository.save(item); model.addAttribute("item",item); return"basic/item"; }
// @PostMapping("/add") public String addItemV2(@ModelAttribute("item") Item item){ itemRepository.save(item); // model.addAttribute("item",item); // 자동추가되므로, 생략 가능 return"basic/item"; } @PostMapping("/add") public String addItemV3(Item item){ //이렇게 작성시 @ModelAttribute가 되고, Model에 등록은 "item"으로 자동으로됨(규칙은 타입 맨앞 대문자 소문자로변경) itemRepository.save(item); return"basic/item"; }
@ModelAttribute에 대해 알아야할점
@ModelAttribute 는 Item 객체를 생성하고, 요청 파라미터의 값을 프로퍼티 접근법(setXxx)으로 입력해준다.
@ModelAttribute 는 중요한 한가지 기능이 더 있는데, 바로 모델(Model)에 @ModelAttribute 로 지정한 객체를 자동으로 넣어준다. 지금 코드를 보면 model.addAttribute(“item”, item) 가 주석처리 되어 있어도 잘 동작하는 것을 확인할 수 있다.
@ModelAttribute(“hello”) Item item 이름을 hello 로 지정
model.addAttribute(“hello”, item); 모델에 hello 이름으로 저장
@ModelAttribute 는 모델에 등록될 이름을 생략하면 클래스의 첫글자만 소문자로 변경해서 등록한다 (Item→item)
@ModelAttribute 자체도 생략가능하다
controller에 추가
Get, Post 나눠서 개발 redirect부분 잘보기
1 2 3 4 5 6 7 8 9 10 11
@GetMapping("/{itemId}/edit") public String editForm(@PathVariable Long itemId, Model model){ Item item = itemRepository.findById(itemId); model.addAttribute("item", item); return"basic/editForm"; } @PostMapping ("/{itemId}/edit") public String edit(@PathVariable Long itemId, @ModelAttribute Item item){ itemRepository.update(itemId,item); return"redirect:/basic/items/{itemId}"; }
@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:/… 으로 편리하게 리다이렉트를 지원한다.
redirect:/basic/items/{itemId}
컨트롤러에 매핑된 @PathVariable 의 값은 redirect 에도 사용 할 수 있다.
redirect:/basic/items/{itemId} {itemId} 는 @PathVariable Long itemId 의 값을 그대로 사용한다.
HTML Form 전송은 PUT, PATCH를 지원하지 않는다. GET, POST만 사용할 수 있다. PUT, PATCH는 HTTP API 전송시에 사용 스프링에서 HTTP POST로 Form 요청할 때 히든 필드를 통해서 PUT, PATCH 매핑을 사용하는 방법이 있지만, HTTP 요청상 POST 요청이다.
PRG Post/Redirect/Get
post로 add하고 새로고침…하면 계속 제품 추가됨..문제점
웹 브라우저의 새로 고침은 마지막에 서버에 전송한 데이터를 다시 전송한다. 상품 등록 폼에서 데이터를 입력하고 저장을 선택하면 POST /add + 상품 데이터를 서버로 전송한다.
이 상태에서 새로 고침을 또 선택하면 마지막에 전송한 POST /add + 상품 데이터를 서버로 다시 전송하게 된다.그래서 내용은 같고, ID만 다른 상품 데이터가 계속 쌓이게 된다.
POST, Redirect GET
웹 브라우저의 새로 고침은 마지막에 서버에 전송한 데이터를 다시 전송한다. 새로 고침 문제를 해결하려면 상품 저장 후에 뷰 템플릿으로 이동하는 것이 아니라, 상품 상세 화면으로 리다이렉트를 호출해주면 된다. 웹 브라우저는 리다이렉트의 영향으로 상품 저장 후에 실제 상품 상세 화면으로 다시 이동한다. 따라서마지막에 호출한 내용이 상품 상세 화면인 GET /items/{id} 가 되는 것이다. 이후 새로고침을 해도 상품 상세 화면으로 이동하게 되므로 새로 고침 문제를 해결할 수 있다.
해결 구현
controller에서 변경
1 2 3 4 5 6
@PostMapping("/add") public String addItemV3(Item item){ //이렇게 작성시 @ModelAttribute가 되고, Model에 등록은 "item"으로 자동으로됨(규칙은 타입 맨앞 대문자 소문자로변경) itemRepository.save(item); // model.addAttribute("item",item); // 자동추가되므로, 생략 가능 return"redirect:/basic/items/"+item.getId(); }
상품 등록 처리 이후에 뷰 템플릿이 아니라 상품 상세 화면으로 리다이렉트 하도록 코드를 작성해보자. 이런 문제 해결 방식을 PRG Post/Redirect/Get 라 한다.
"redirect:/basic/items/" + item.getId() redirect에서 +item.getId() 처럼 URL에 변수를 더해서 사용하는 것은 URL 인코딩이 안되기 때문에 위험하다. 다음에 설명하는 RedirectAttributes 를 사용하자.
RedirectAttributes
상품을 저장하고 상품 상세 화면으로 리다이렉트 한 것 까지는 좋았다. 그런데 고객 입장에서 저장이 잘 된 것인지 안 된 것인지 확신이 들지 않는다. 그래서 저장이 잘 되었으면 상품 상세 화면에 “저장되었습니다”라는 메시지를 보여달라는 요구사항이 왔다. 간단하게 해결해보자.
RedirectAttributes 를 사용하면 URL 인코딩도 해주고, pathVarible , 쿼리 파라미터까지 처리해준다.