ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스프링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
Designed by Tistory.