서비스 제공흐름
검은색 박스가 컨트롤러 흰색 박스가 뷰이다.
즉 항상 뷰를 컨트롤러를 통해 접근해야한다.
항상 상품 목록에서 시작해서 상품 등록 폼, 상품 상세페이지->상품 수정 폼으로 이동하도록 설계했다.
상품에는 상품 id와 이름, 가격 수량을 설정했다.
가격과 수량에는 0이 들어갈 수 있게 int가 아닌 Integer로 설정하였다.
itemRepository를 만들었다.
우선, private static final로 Map을 만들어서 싱글톤방식으로 다른 곳에서도 동일한 Map을 사용하게 하였다.
왜냐하면 Controller애노테이션으로 인해 스프링 빈으로 올라갈때 한번 Map이 만들어지면 불변으로 다른곳에서 생성 수정이안되기 때문이다. static을 통해 다른곳에서도 이용이 가능하다.
save메서드를 보면
id를 sequence를 추가해서 설정하고 put메서드로 Map에 넣어줬다.
findById메서드를 보면 get 메서드를 통해 id를 가져왔다.
finaAll은 반환형이 List형식으로 store에 있는 모든 Item들을 반환하였다.
update같은경우 findById메서드를 통해서 Item을 가져오고, setter를 통해서 updateParam의 값으로 바꿔주었다.
사실 여기서 Item객체보다는 ItemDto같은 클래스를 만들어서 사용하는 것이 좋은데 그 이유가 여기서 updateParam의 Item클래스에서는 id를 사용하지 않기 때문에 id를 제외한 ItemDto 클래스를 만들어서 이용하는것이 좋다.
프로젝트 사이즈가 작기 때문에 그냥 사용하였다.
Test
afterEach를 통해 한 테스트가 끝나면 store를 비워줬다.
테스트를 전부 통과하였다.
우선 부트스트랩을 깔아주자. 웹사이트를 쉽게 만들어주는 HTML,CSS,JS 프레임워크이다.
부트스트랩을 다운로드 받고 압축을 풀자.
일단 타임리프 부분은 생략하고 넘어가겠다. 왜냐하면 일단 실제 지금 개발하는 프로젝트 자체가 우선 CSR을 사용하고 있고, 실제 백엔드단에서는 DATA만 넘겨주고 이 DATA를 앞단에서 렌더링하는게 추세이기 때문,,, 잘 알지 못하면 적지 않는게 더 맞다고 생각한다.
그래서 이번파트는 SSR과 CSR에 대해서 설명하고 넘어가고자한다.
SSR vs CSR
items메서드는 모든 items를 찾은다음에 model에다가넘겨주고 반환을 view로 한다. 이러면 Controller인데 String이면 논리적 뷰로 viewPath를 통해서 뷰를 찾게 된다.
items메서드는 item하나만 찾는것이다.
addForm 메서드는 그냥 말그대로 상품을 등록할 수 있는 폼으로 이동하는것이다.
상품추가 메서드
일단 상품추가후 view에서 모델이서 data를 꺼낼때 item이라는 이름으로 꺼낸다.
즉 한마디로, 저장하고 난 뒤에 item을 모델에 넣어줄때 키를 "item"으로 설정하고 넣어줘야한다는것이다.
우리는 저장할때 Http form에서 data를 넘겨주므로 type이 xxx-urlform 어쩌구 이거다. 그래서 @ModelAttribute를 사용하면 Item에 맞는 요청파라미터 값을 프로퍼티 접근법으로 입력해준다.
그런데 ModelAttribute의 기능은 이렇게 객체를 만들어주는것 뿐만아니라 Item 의 앞글자만 소문자로 바꿔서 item으로 모델에 넣어주는역할이 있다.
그래서
이런식이 아니라,
이렇게 model에 addattribute를 하지 않아도 자동적으로 모델에 들어간다.
참고로. 만약 model에서 꺼낼때 key값을 항상 소문자하나로 바꿔서 넣을 수 있는건 아니다. 이름을 지정해주면된다.
이런식으로 하면 model에는 model.addAttribute("newkey",item); 이런식으로 되는거다.
우선 getMapping으로 edit폼을 보여주는것이다. 그래서 1번 item을 수정하고 싶으면 1번 item을 찾아서 model에다가 넣어서 수정 폼으로 이동한다.
실제 상품 수정 처리
여기서 redirect를 사용하는데 뷰템플릿을 호출하는대신에 상품 상세 화면으로 리다이렉트를 호출한다.
지금까지 진행한 상품 등록컨트롤러는 문제가있다.
상품 저장까지 하고 새로고침을 누르면 다시 저장이 되는것이다 이게 무슨말이냐 하면,
상품등록폼으로가서 상품정보를 입력하고
저장버튼을 누르면 상품 상세 페이지로 이동하게 된다. 왜냐하며 return값이 컨트롤러에서
상품상세 뷰인 /basic/item으로 이동하게 했기 때문이다.
그다음 여기서 새로고침을 누르면
그림처럼 동일한 상품이 똑같이 등록된다.
왜그럴까?
순서대로 보면
1. 우리는 get/add로 폼을 요청해서 지금 화면으로 받았다.
2. 여기서 상품정보를 입력하고 요청을 보냈다.
3. 이러면 서버에서 요청을 처리하고 상품등록 폼을 보여줬다.
이러면 마지막 요청이 바로 Post/add + 상품데이터이다.
새로고침은 마지막에한 요청을 다시 하는것이므로 이상태에서 새로고침을 하면 마지막에 전송한 Post/add + 상품데이터가 다시 작동하는것이다.
그러므로 어떻게 해결해야하냐면
이런식으로 post를 하면 redirect를 요청하는것이다. 그러면 클라이언트쪽에서 Redirect를 받으면 다시서버측에 해당 Redirect url로 요청을 보내므로, 처음에 Post로 요청을 보냈으나, Redirect로 인해 마지막으로 보낸요청은 Get/items가 된다.
그러므로 새로고침을 눌러도 get요청을 다시 보내게 되는것이다.
이렇게 Post/add에서 redirect로 요청을하게 되면 GetMapping의 /{itemId}로 요청을 다시 보내게 된다.
/basic/items는 컨트롤러 상단에 RequestMapping으로 붙여뒀다.
그런데 상품 상세 화면으로 리다이렉트는 잘했는데 이게 고객입장에서 제대로 저장이 되었는지 안되었는지 헷갈리게 된다. 그래서 저장이 잘 되었으면 상품 상세화면에 저장되었습니다 라는 메세지를 띄우고 싶은데 redirectAttributes를 사용하자.
우리는 리다이렉트로 get /basic/items/{itemId}를 보내야하므로, itemId를 redirectAttributes에 넣어주자. 이러면 redirect할때 {itemId}에 자동적으로 내가 넣어준 값이 들어가게 된다.
그다음에 status를 true로 설정한게 redirect url에 들어갈 곳이 없는데, 이러면 /basic/items/1?status=ture 이런식으로 넣어준다.
그러면 앞단에서 Post/add로 요청을 보냈는데 status가 true인 쿼리 파라미터를 받으면, 저장이완료되었다는 팝업을 띄워주면 된다.