-
[스프링MVC - 1편] 스프링 MVC - 웹 페이지 만들기 (2)스프링&스프링부트 2025. 1. 30. 19:33
상품 목록 - 타임리프
BasicItemController
@Controller @RequestMapping("/basic/items") @RequiredArgsConstructor public class BasicItemController { private final ItemRepository itemRepository; @GetMapping public String items(Model model) { List<Item> items = itemRepository.findAll(); model.addAttribute("items", items); return "basic/items"; } // 테스터용 데이터 추가 @PostConstruct public void init() { itemRepository.save(new Item("ItemA", 10000, 10)); itemRepository.save(new Item("ItemB", 20000, 7)); } }
@RequiredArgsConstructor
// 1. @Controller @RequestMapping("/basic/items") public class BasicItemController { private final ItemRepository itemRepository; @Autowired public BasicItemController(ItemRepository itemRepository) { this.itemRepository = itemRepository; } } // 2. @Controller @RequestMapping("/basic/items") public class BasicItemController { private final ItemRepository itemRepository; // @Autowired // 생성자 1개일 땐, 생략 가능 public BasicItemController(ItemRepository itemRepository) { this.itemRepository = itemRepository; } } // 3. 롬복 @RequiredArgsConstructor 사용 시 final 생성자 만들어준다 @Controller @RequestMapping("/basic/items") @RequiredArgsConstructor public class BasicItemController { private final ItemRepository itemRepository; }
items.html > 타임리프 사용하도록 수정
- 타임리프 사용 선언
- URL 링크 표현식 - @{...}
- 속성 변경 - th:onclick
- 리터럴 대체 - |...|
- 반복 출력 - th:each
- 변수 표현식 - ${...}
- 내용 변경 - th:text
- URL 링크 표현식 2 - @{...}
타임리프 핵심
핵심은 th:xxx가 붙은 부분은 서버사이드에서 렌더링 되고, 기존 것을 대체한다
th:xxx이 없으면 기존 html의 xxx 속성이 그대로 사용된다
HTML을 파일로 직접 열었을 때, th:xxx가 있어도 웹 브라우저는 th: 속성을 알지 못하므로 무시한다
따라서 HTML을 파일 보기를 유지하면서 템플릿 기능도 할 수 있다
순수 HTML을 그대로 유지하면서 뷰 템플릿도 사용할 수 있는 타임리프의 특징을 네츄럴 템플릿이라고 한다
상품 상세
BasicItemController에 추가
@GetMapping("/add") public String addForm() { return "basic/addForm"; }
상품 등록 폼
addItemV1 - BasicItemController에 추가 (@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"; }
addItemV2 - 상품 등록 처리 (ModelAttribute)
@PostMapping("/add") // public String addItemV2(@ModelAttribute("item") Item item, Model model) { public String addItemV2(@ModelAttribute("item") Item item) { itemRepository.save(item); // @ModelAttribute 자동으로 추가해 준다, 생략가능 // model.addAttribute("item", item); return "basic/item"; }
- @ModelAttribute - 요청 파라미터 처리
- @ModelAttribute는 Item 객체를 생성하고, 요청 파라미터의 값을 프로퍼티 접근법(setXxx)으로 입력해 준다
- @ModelAttribute - Model 추가
- @ModelAttribute는 중요한 한가지 기능이 더 있는데, 바로 모델(Model)에 @ModelAttribute로 지정한 객체를 자동으로 넣어준다
- 이름은 @ModelAttribute에 지정한 name(value) 속성을 사용한다
addItemV3 - 상품 등록 처리 (ModelAttribute 이름 생략)
/** * @ModelAttribute name 생략 가능 * model.addAttribute(item); 자동 추가, 생략 가능 * 생략시 model에 저장되는 name은 클래스명 첫글자만 소문자로 등록 Item -> item */ @PostMapping("/add") public String addItemV3(@ModelAttribute Item item) { itemRepository.save(item); return "basic/item"; }
- 주의
- @ModelAttribute의 이름을 생략하면 모델에 저장될 때 클래스명을 사용한다 이때 클래스의 첫 글자만 소문자로 변경해서 등록한다
- 예) @ModelAttribute 클래스명 > 모델에 자동 추가되는 이름
- Item > item
- HelloWorld > helloWorld
addItemV4 - 상품 등록 처리 (ModelAttribute 전체 생략)
@PostMapping("/add") public String addItemV4(Item item) { itemRepository.save(item); return "basic/item"; }
상품 수정
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}"; }
상품 수정은 상품 등록과 전체 프로세스가 유사하다
- GET /items/{itemId}/edit : 상품 수정 폼
- POST /items/{itemId}/edit : 상품 수정 처리
PRG Post/Redirect/Get
지금까지 진행한 상품 등록 처리 컨트롤러는 심각한 문제가 있다. (addItemV1 ~ addItemV4)
상품 등록을 완료하고 웹 브라우저의 새로고침 버튼을 클릭해보면 상품이 계속해서 중복 등록되는 것을 확인할 수 있다
웹 브라우저의 새로 고침은 마지막에 서버에 전송한 데이터를 다시 전송한다
상품 등록 폼에서 데이터를 입력하고 저장을 선택하면 POST /add + 상품 데이터를 서버로 전송한다
이 상태에서 새로 고침을 또 선택하면 마지막에 전송한 POST /add + 상품 데이터를 서버로 다시 전송하게 된다
그래서 내용은 같고, ID만 다른 상품 데이터가 계속 쌓이게 된다
POST, Redirect GET
웹 브라우저의 새로 고침은 마지막에 서버에 전송한 데이터를 다시 전송한다
새로 고침 문제를 해결하려면 상품 저장 후에 뷰 템플릿으로 이동하는 것이 아니라, 상품 상세 화면으로 리다이렉트를
호출해 주면된다
웹 브라우저는 리다이렉트의 영향으로 상품 저장 후에 실제 상품 상세 화면으로 다시 이동한다
따라서 마지막에 호출한 내용이 상품 상세 화면인 GET /items/{id} 가 되는 것이다
이후 새로고침을 해도 상품 상세 화면으로 이동하게 되므로 새로 고침 문제를 해결할 수 있다
BasicItemController에 추가
@PostMapping("/add") public String addItemV5(Item item) { itemRepository.save(item); return "redirect:/basic/items/" + item.getId(); }
RedirectAttributes
BasicItemController에 추가
@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
RedirectAttributes를 사용하면 URL 인코딩도 해주고, pathVariable, 쿼리 파라미터까지 처리해준다
- redirect:/basic/items/{itemId}
- pathVariable 바인딩: {itemId}
- 나머지는 쿼리 파라미터로 처리: ?status=true
뷰 템플릿 메시지 추가
<h2 th:if="${param.status}" th:text="'저장완료'"></h2>
끝 ! 728x90'스프링&스프링부트' 카테고리의 다른 글
[스프링MVC - 2편] 타임리프 기본기능 (2) (1) 2025.02.01 [스프링MVC - 2편] 타임리프 기본기능 (1) (0) 2025.01.31 [스프링MVC - 1편] 스프링 MVC - 웹 페이지 만들기 (1) (0) 2025.01.30 [스프링MVC - 1편] 스프링 MVC 기본기능 - 4 (1) 2025.01.29 [스프링MVC - 1편] 스프링 MVC 기본기능 - 3 (0) 2025.01.29