
상품 서비스 HTML
/resources/static : 스프링 부트가 정적 리소스를 제공
실제 서비스에서도 공개되어 공개할 필요없는 HTML을 두는 것은 주의해야한다./resources/template : 스프링 부트가 동적 리소스를 제공
상품 목록 - 타임리프
 
@RequiredArgsConstructor
//생성자가 하나만 있으면 해당 생성자에 자동으로 @Autowired 의존관계 주입
public BasicItemController(ItemRepository itemRepository) {
    this.itemRepository = itemRepository;
}타임리프 사용 선언
<html xmlns:th="http://www.thymeleaf.org">
속성변경
th:href="@{/css/bootstrap.min.css}"
HTMl을 그대로 볼 때는 href 속성이 사용되고, 뷰 템플릿을 거치면 th:href의 값이 href로 대체되면서 동적으로 변경할 수 있다.
타임리프 핵심
URL 링크 표현식 = @{...}
상품 등록 폼으로 이동, 속성 변경 - th:onclick
리터럴 대체 - |...|
반복 출력 - th:each
<tr th:each="item : ${items}">변수 표현식 - ${...}
<td th:text="${item.price}">10000</td>내용 변경 - th:text
<td th:text="${item.price}">10000</td>URL 링크 표현식2 - @{...}
th:href="@{/basic/items/{itemId}(itemId=${item.id})}"URL 링크 간단히
th:href="@{|/basic/items/${item.id}|}"JSP vs Thymeleaf
JSP 파일은 웹 브라우저에서 그냥 열면 JSP 소스코드와 HTML이 뒤죽박죽 되어서 정상적 확인 불가능
Thymeleaf는 순수 HTML 파일을 웹 브라우저에서 확인 가능하고, 서버를 통해 뷰 템플릿을 거치면 동적으로 변경된 결과 또한 확인이 가능하다.
=> 네츄럴 템플릿 (=natural templates)상품 상세
BasicItemController에 추가 
@GetMapping("/{itemId}")
    public String item(@PathVariable Long itemId, Model model) {
		Item item = itemRepository.findById(itemId); //PathVariable로 넘어온 상품 ID로 상품을 조회
     	model.addAttribute("item", item); //모델에 담음
     	return "basic/item"; //뷰 템플릿 호출
}상품 등록 폼
  @GetMapping("/add")
  public String addForm() {
      return "basic/addForm";
  }<form action="item.html" th:action method="post">상품 등록 처리 - @ModelAttribute
 //요청 파라미터 형식을 처리해야 하므로 @RequestParam을 사용
 
 @PostMapping("/add")
 public String addItemV1(@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/item"; //상품 상세에 사용한 item.html 뷰 템플릿을 그대로 재활용
 }
 
 /**
* @ModelAttribute("item") Item item
* model.addAttribute("item", item); 자동 추가 */
  @PostMapping("/add")
  public String addItemV2(@ModelAttribute("item") Item item, Model model)   { 
itemRepository.save(item); //model.addAttribute("item", item); //자동 추가, 생략 가능
      return "basic/item";
  }
  
  //클래스의 첫 글자만 소문자로 바꿔서 등록
   @PostMapping("/add")
  public String addItemV3(@ModelAttribute Item item) {
      itemRepository.save(item);
      return "basic/item";
  }
  
  //ModelAttribute 전체 생략
   @PostMapping("/add")
   public String addItemV4(Item item) {
      itemRepository.save(item);
      return "basic/item";
   }ModelAttribute 2가지 역할
1. 요청 파라미터 처리 : Item 객체를 생성하고, 요청파라미터의 값을 프로퍼티 접근법(setXXX) 입력
2. Model 추가 : @ModelAttribute에 지정한 name(value) 속성을 사용. 
만약 이름과 객체 이름이 다르다면

상품 수정
//BasicItemController에 추가
@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}";
}상품 수정과 상품 등록은 전체 프로세스가 유사하므로 설명은 패스
리다이렉트
PRG Post/Redirect/Get
상품 등록을 한 후 새로고침을 하면 같은 폼이 계속 중복돼서 등록된다
-> 웹 브라우저의 새로고침은 마지막에 서버에 전송한 데이터를 다시 전송
-> 즉, 마지막이 POST /add 이므로 POST 방식을 다시 전송하게 된다.
새로고침문제를 해결하기 위해 상품 저장 후에 뷰 템플릿으로 이동하는 것이 아니라, 상품 상세 화면으로 리다이렉트 호출
-> 따라서 마지막에 호출한 내용이 상품 상세 화면인 GET /items/{id} 가 된다.
 
//BasicItemController에 추가
 /**
  * PRG - Post/Redirect/Get
  */
   
@PostMapping("/add")
public String addItemV5(Item item) {
      itemRepository.save(item);
      return "redirect:/basic/items/" + item.getId(); // URL 인코딩이 안되어 위험 -> RedirectAttributes 사용하자
}RedirectAttributes
고객에게 "저장되었습니다" 라는 메세지를 띄워보자.
/**
 * 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}";
}RedirectAttributes
<h2 th:if="${param.status}" th:text="'저장 완료!'"></h2>정리
Reference
김영한 님 - 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술