스프링/3. 스프링 MVC

55. 컨트롤러 구현

sdafdq 2023. 8. 15. 20:39
package hello.itemservice.web.basic;

import hello.itemservice.domain.item.Item;
import hello.itemservice.domain.item.ItemRepository;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

@Controller
@RequestMapping("/basic/items")
@RequiredArgsConstructor
public class BasicItemController {
    private final ItemRepository itemRepository;

    @GetMapping
    public String items(Model model){
        model.addAttribute("items",itemRepository.findAll());
        return "/basic/items";
    }

    @GetMapping("/{itemId}")
    public String item(@PathVariable long itemId, Model model){
        model.addAttribute("item",itemRepository.findById(itemId));
        return "basic/item";
    }

    @GetMapping("/add")
    public String addForm(){
        return "basic/addForm";
    }

    @PostMapping("/add")
    public String add(Item item, RedirectAttributes redirectAttributes){
        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("succesed", true);
        return "redirect:/basic/items/{itemId}";
    }

    @GetMapping("/{itemId}/edit")
    public String editForm(@PathVariable long itemId, Model model){
        model.addAttribute("item",itemRepository.findById(itemId));
        return "basic/editForm";
    }

    @PostMapping("/{itemId}/edit")
    public String edit(@PathVariable long itemId, Item item){
        itemRepository.update(itemId, item);
        return "redirect:/basic/items/{itemId}";
    }

    @PostConstruct
    public void init(){
        Item item1 = new Item("itemA",10000,10);
        Item item2 = new Item("itemB",20000,20);
        itemRepository.save(item1);
        itemRepository.save(item2);
    }
}

이 컨트롤러의 기본, 상위 url은 /basic/items로 해뒀다.

 

처음 items는 RequestMapping Url을 따로 안뒀으므로, /basic/items가 된다. 즉, http://localhost:8080/basic/items

전체 조회해서, items라는 이름으로 모델에 넘겨주고, /basic/items라는 (이건 위와 다른 뷰템플릿의 위치다. 그냥 동명이인이라고 생각하셈.) 논리 이름을 return해 준다. 그럼 알아서 viewResolver가 html이나 앞에 물리이름등을 붙이고 view에 담아서 dispatcherServlet에 넘겨준다.

 

그 다음은 GET방식으로  /basic/items/{뭔가로 넘겨올 때}

를 받는거다. 근데 이거 아무래도 우선순위가 좀.. 나중인 모양이다.

그러니까, 예를 들어 저기는 @PathVariable long itemId 해서 long타입으로 받았지만, 만약 저게 String이었다면? 그럼 /add와 겹쳤을 것이다. 아무래도 경로변수 있는 방식은 /add 같은 고정보다 우선순위가 낮은 모양이다. 그래서 아마 예를 들면     @GetMapping("/{itemId}/edit")     @GetMapping("/aaa/edit") 이런 거 있으면 /aaa/edit 먼저 검사할 듯.

여튼 GET방식으로 /{itemId}해서 경로변수로 받고, 그거 조회해서 모델에 "item"이라는 이름으로 넣어준 다음에 basic/item 이라는 논리이름을 반환한다. 뭐 그럼 알아서 뷰리졸버가 template/basic/item.html 이런 식으로 해서 해 주겠지.

근데 주의할 게

@GetMapping("/{itemString}")
public String test(@PathVariable String itemString, Model model){
    return "basic/item";
}

@GetMapping("/{itemId}")
public String item(@PathVariable long itemId, Model model){
    model.addAttribute("item",itemRepository.findById(itemId));
    return "basic/item";
}

이런 식이면 저기로 들어온 게 long인지 string인지 분간을 못해서 에러난다.

 

그리고, html에서 링크 이동하거나 그런 것의 기본은 GET방식인 듯 하다. POST는 Form을 써야 하는 듯. 그거 외에는 다 GET방식이라고 생각하면 좋을 듯.

 

그 다음 addForm은 그냥 addForm으로 유도.

 

그 다음은 똑같이 ("/add")인데 post로 받았다. 그러니까 같은 "/add"라도 GET방식으로 들어오면 그냥 form을 보여주는 것이고, POST방식으로 들어오면 다른 것으로 처리하게끔, 저장하는 걸로 처리하게끔 했다.

아 참고로, 저 Post방식의 /add 보면 model.addAttribute()가 없는데, 왜냐하면 Item item을 @ModelAttribute로 받았기 때문이다. 이게 무슨 말이냐면, 이름 딱 봐도 모델과 관계있어 보인다. 즉, 모델에 저렇게 하면 Item이 자동으로 들어간다. 그리고, 여기선 좀 특이한 게 변수 이름이 아니라 클래스 이름이 중요하다고 한다. 그러니까, 만약 저게 Item item이 아니라 Item productItem이라고 했어도 key가 item으로 들어간다는 말이다. 클래스이름 첫글자를 소문자로 바꾸고 들어간다.

 

또 RedirectAttributes 이거는 리다이렉트와 관련해서 지원해주는 클래스이다.

@PostMapping("/add")
public String add(Item item, RedirectAttributes redirectAttributes){
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("succesed", true);
    return "redirect:/basic/items/{itemId}";
}

보면 이렇게 우리가 리다이렉트를 쓴다. 저렇게 "redirect:경로" 이런 식으로 쓸 수 있다. 이 리다이렉트로 return가는 것은 아무래도 뷰템플릿 경로가 아니라.. url경로다. 다시 클라이언트에게 돌아가 이걸로 접속하게끔 해야 하므로.

다시, 이렇게 보면 할거 하고, 저런 식으로 리다이렉트에 대한 경로에 대한 걸 도와주는 거다.

보면 {itemId} 저렇게 치환이 가능하다.

물론 return "redirect:/basic/items/" + savedItem.getId() 이것도 된다.

근데, url경로는 한글이나 띄어쓰기 등등 이런 게 들어가면 안되서 위험하다.

그래서 저 RedirectAttributes 라는 것의 역할은 그러한 것을 인코딩 해주는 역할이다.

 

또, 여기서 왜 리다이렉트 했는지 궁금할 수도 있다.

일단 새로고침의 역할은, 가장 마지막에 보냈던 요청을 다시 보내는 것이다.

여기서 우리가 보면 

    @GetMapping("/add")
    public String addForm(){
        return "basic/addForm";
    }

    @PostMapping("/add")
    public String add(Item item, RedirectAttributes redirectAttributes){
        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("succesed", true);
        return "redirect:/basic/items/{itemId}";
    }

이렇게 두 종류로 /add를 받는다.

만약 처음 /add로 들어가 등록양식 html을 응답받고, 그 다음

그 양식을 입력해 submit으로 보내면, 우리가 그 폼의 메소드를 post로 해놨기 때문에 저 두번째 /add로 갈것이다.

만약, 그 후 우리가 리다이렉트를 안한다면 여전히 사용자의 위치는 http://localhost:8080/basic/items/add이다.

내가 새로고침의 역할은 가장 마지막 요청을 다시 보내는 것이다. 그럼 이때 우리는?

가장 마지막으로 Post를 요청했다. 여기서 새로고침을 하면 다시 그것이 요청이 되어진다.

즉, 우리가 뭔갈 주문한 후에, 다시 새로고침하면 그게 그대로 한번 더 주문되는거다.

이걸 방지하기 위해, 우리는 redirect를 보내는 것이다. 기본적으로 뭔가 표시한 게 없으면, get방식이기 때문에, "redirect:/basic/items/{itemId}" 이렇게 해 두면 저것이 뷰템플릿이 아니라 클라이언트의 url경로가 되는 것이다. 

그러면 따로 뭔가 한게 없으면, 그러니까 기본 default가 get방식이기 때문에 post가 아니라 get방식으로 유저가 요청한 걸로 되어진다. 

보통 유저에게 응답하기 전까지 리다이렉트나 이런 거 없으면 왜 뷰템플릿만 해도 서버내에서 이동해서 처리하는거다.

 

그리고, RedirectAttributes 에서 저렇게 따로 경로변수가 없다면, 쿼리로 들어간다. 그래서 저게 실행이 되면

http://localhost:8080/basic/items/3?succesed=true 이런 식으로 간다. 

 

 

 

그 다음 edit도 비슷하다.

{itemId}로 받고 그걸로 Item 찾아서 그에 맞춘 form을 보내주고, (폼 자체가 edit버튼 누르면 가는 링크 자체를 id넣어서 가게끔 함. 거기다 수정은 원래 기존 썼던 정보 있어야 하니까.) ItemRepository의 update()는 받은 Item을 기존 itemId에서 받은 걸로 찾아서 거기다 치환시켜주는 함수다. 그래서 인자의 Item은 일회용이지만, ItemId로 다시 조회하는거기 때문에 상관없다. 그렇게 저건 수정 후 수정된 걸 보여주기 위해 아이템 상세로 리다이렉트 시켜버린다.

 

 

'스프링 > 3. 스프링 MVC' 카테고리의 다른 글

54. 타임리프 템플릿  (0) 2023.08.15
53. 도메인  (0) 2023.08.14
52. 요구사항  (0) 2023.08.14
51. 요청 파라미터 및 메시지바디의 데이터 정리  (0) 2023.08.13
50. 스프링MVC 구조 전체정리.  (0) 2023.08.13