당니의 개발자 스토리

상품 주문 본문

스프링/실전! 스프링 부트와 JPA 활용1

상품 주문

clainy 2024. 5. 19. 14:52

상품 주문

이번 시간에는 상품 주문을 한번 만들어 보겠습니다.

상품 주문을 만들고 주문 내역까지 하면 완성이 되겠네요.

자 상품 주문은 제가 /order 라는 페이지로 가도록 해놨는데요.

OrderController부터 만들어 보면 되겠죠.

여기까지 자동적으로 어노테이션을 적어주고, OrderController는 예전에 완성된 화면을 보셨던 분들은 기억하겠지만, 고객이랑 Item을 다 선택을 할 수 있어야 되거든요. 그래서 좀 dependency가 많이 필요합니다.

이렇게 dependency하는 애들을 적어주면 됩니다. 고객이랑 상품이랑 다 선택을 해야 되니까요.

자 이제 createForm을 만들고 코딩하면 되겠죠. 이렇게 반환까지 해줬습니다.

그러니까 상품 주문을 클릭하면 orderForm이 렌더링된 화면을 보는 겁니다.

그럼 이제 orderForm.html을 만들어줘야 되겠죠.

그런 다음에 복사 붙여넣기 해줍니다.

화면을 보고 설명드리는 게 나아서 먼저 돌려보겠습니다.

상품 주문으로 들어가시면,

회원이랑 상품을 선택한 다음에 주문 수량 고르고 summit 딱 누르면 됩니다. 이 화면이 어떻게 렌더링 됐는지를 보기 전에,

지금 회원이랑 상품이 없으니까 회원부터 하나 넣어볼게요.

그리고 상품도 일단 아무 값이나 넣고 Submit한 다음, 상품 주문으로 들어가면,

이제 회원이랑 상품명을 고를 수 있게 되었습니다.

이제 주문수량까지 적고 Submit 누르면 되겠죠. 어쨌든 이 화면이 어떻게 구성 되는지 살펴보도록 하겠습니다.

OrderController에서는 저희가 만들어놓은 MemberService의 findMembers로 모든 Member를 끌고 오고, ItemService에서 findItems로 Item들을 다 가져온 다음에, 그것들을 model에 담아서 orderForm.html으로 넘깁니다.

그러면 이제 orderForm에 렌더링 하면 되겠죠.

어떻게 렌더링을 해야하냐면,

제일 먼저 주문 회원Select Box를 만들어야 되겠죠. 지금은 회원이 하나라서 test 밖에 안 보이는데, 만약 회원이 여러 명이면 여러 명이 나와야 되겠죠.

보시면 저희가 잘 아는 HTMLselect를 사용한 걸 보실 수 있는데,

여기 타임리프에 th:each라고 있습니다.

이 each로 방금 저희가 model에 담았던 members, 모든 회원의 목록

옵션에다가 쭉 돌립니다. 그래서 submit 하면 여기서 선택된 값이 넘어가겠죠.

그 다음에 상품도 마찬가지입니다. 똑같이 이 Select를 사용해서 이렇게 th:each로 attribute를 가지고 이렇게 돌립니다. 그러면 valueid가 되고 textname이 돼서 쭉 나오게 되죠.


그러니까 실제로는 member.name(JPA)이 select box에 뜨는 건데, value는 memberId 라는 거죠.

주문수량은 그냥 input으로 입력을 하고,

그 다음에 form submit을 딱 하면 어디로 가겠습니까?

자 여기 보시면 action/orderpost 방식으로 보내지게 됩니다.

따라서 여기로 보내지는데 지금은 안 만들었으니까 없겠죠.

자 그럼 이제 만듭시다.

@RequestParam 어노테이션은 사용자가 요청시 전달하는 값을 Handler(Controller)의 매개변수 1:1 맵핑할때 사용되는 어노테이션입니다.

참고로 @RequestParamFormSummit 방식이라고 합니다.

이렇게 FormSummit 방식으로 오면, <select> </select>에서 memberId 해놨죠.

그리고 여기 선택된 "itemId"에 대응하는

value가 @RequestParam에 의해 넘어오게 됩니다.

그래서 memberId, itemId,

그리고 count 값까지 해서 @PostMapping(value = "/order")으로 넘어오게 됩니다.

여기 있는 "name"들로 넘어올 거에요. 자 이렇게 되면 @RequestParam으로 매칭이 돼서 딱딱딱 넘어오고 이 변수들에 바인딩 한 거죠.


바인딩(Binding)이란 프로그램의 어떤 기본 단위가 가질 수 있는 구성요소의 구체적인 값, 성격을 확정하는 것을 말한다.


자 그 다음에 어떻게 합니까?

orderServiceorder 만든 걸 써야죠. 인텔리 제이가 좋은 게 파라미터를 자동으로 추천해줍니다.

그래서 이렇게 파라미터 3개를 딱 넘기면 order 로직이 돌아가겠죠. 그리고 뭐 특별한 문제가 있으면 예외가 터지게 돼있고, 문제가 없으면 정상이거든요.

그래서 끝나면 redirect 해서 /orders 라고 만들어 둘 겁니다. 이 orders 는 주문내용 목록이고 다음 시간에 볼 거에요.

자 이렇게 해놓고 한번 확인해 볼게요. 일단 서버를 다시 띄우겠습니다.

자 여기 보시면 memberId, itemId, count죠. 그러니까 '어떤 고객이 어떤 Item을 주문할 건데, 몇 개의 수량으로 주문할 거야'

지금 화면을 보시면 상품을 선택하는데, 멀티로 하지 않고 상품 하나만 선택할 수 있게 해놨어요. 이건 여러분들이 조금만 고쳐보시면 멀티로 로직을 바꿀 수 있겠죠.

하나의 상품이 아니라 여러 개의 상품을 한 번에 주문할 수 있도록 하고 싶으면 이쪽만 살짝 고쳐주시면 됩니다. 예제에서는 단순화 하기 위해서 우선 하나의 상품만 주문하도록 했습니다.

물론 핵심 도메인 로직에서는 2개, 3개 주문할 수 있도록 설계가 되어 있죠.

그래서 보시면 memberId랑 itemId를 OrderService에서 어떻게 합니까?

이게 OrderService에 있는 Order 거든요. 그럼 이제 매개변수로 받은 Id를 통해서 Member 엔티티를 찾고 Item 엔티티를 찾습니다.

그 다음에 쭉 로직이 돌겠죠.

order에 커서를 두고 cmd + B 하면 이동을 할 수가 있어요.

다시 OrderController로 가볼게요.

참고로 이걸 보고 그런 고민을 할 수가 있거든요. 'Controller에서 id를 그대로 넘기지 말고, 먼저 Member랑 Item을 찾아서 넘겨도 되지 않나요?' 네 물론 그렇게 하셔도 됩니다.

자 그런데 그냥 이렇게 하는 게 뭐가 좀 더 좋냐면, Controller에서 엔티티들을 직접 다 찾으면 Controller 로직도 되게 지저분해지는 게 있겠지만, 이게 뭔가 테스트 하거나 할 때도 그렇고 id로 찾는 게 더 단순화 할 수 있는거죠.

여기서 Id만 딱딱 넘겨서 트랜잭션 안(@Transactional)에서 뭔가 jpa가 동작할 때가 제일 깔끔하게 동작하거든요.

그래서 여기서는 딱 식별자만 넘기는 게 좋습니다. 생각해보면 바깥 입장(Controller)에서는 엔티티나 이런 걸 잘 몰라도 되는 건데, 서비스 계층에서는 아무래도 엔티티에 대해서 더 많이 의존한다는 관점에서 보면, 물론 바깥에서 엔티티를 찾아서 넘기셔도 되지만 그것보다는 안에서 찾는 게 repository도 의존하니까 엔티티를 바로 찾을 수 있는 등, 할 수 있는 게 더 많아져요.

@Transactional 안에서는 이 엔티티들도 영속 상태로 흘러가기 때문에 훨씬 더 깔끔하게 문제들이 해결이 됩니다.

개인적으로는 주로 command성, 예를 들면 주문 같은 거는 외부 Controller 레벨에서는 그냥 식별자만 넘겨요. 일반 조회는 상관이 없습니다.

그럼 실제 핵심 비즈니스 서비스에서 어떻게 하냐면 엔티티를 찾는 것부터 다 거기서 하는 거예요. 그렇게 하면 이 엔티티 값들도 지금 @Transactional 안에서 엔티티를 조회를 해야 이게 영속 상태로 진행이 된단 말이에요. 그럼 Member나 Item의 상태도 바꿀 수가 있고요. 그렇기 때문에 가급적이면 Controller에서 조회가 아닌, 핵심 비즈니스 로직이 있는 것 같은 경우에는 바깥에서 엔티티를 찾아서 넘기는 것보다는

이렇게 식별자만 넘겨주고 핵심 비즈니스 로직은

이 안에서 하게 되면, 어쨌든 영속 컨텍스트가 존재하는 상태에서 엔티티들을 조회할 수 있단 말이에요. 그러면 주문하면서 Member가 바뀌거나 Item이 바뀌더라도 Dirty Checking 이라고 하죠.

이런 식으로 값을 바꾸더라도 자연스럽게 적용이 되는데, 바깥에서 엔티티들을 가지고 넘어와버리면 아무것도 적용이 안됩니다.

왜냐면 지금 @Transactional 없이 바깥에서 조회한 게 들어온 거잖아요.

그러면 매개변수로 들어온 애는 영속 상태가 끝난단 말이에요. 그 상태로 order에 넘어가면,

예를 들어서 여기서 Id 대신에

Member가 넘어와버리면 이 Member는 JPA랑 관계가 없는 애가 넘어온단 말이에요. 현재 이 트랜잭션과 관련된 영속성 컨텍스트랑 관계 없는 애가 넘어오게 되면 member나 item의 값을 바꾸려고 하면 되게 애매해져요.

그래서 그냥 깔끔하게 memberId랑 count만 이렇게 넘기는 게 좋습니다. 이렇게 하는게 테스트 할 때에도 훨씬 좋아요.

 

자 그렇게 해서 어쨌든 Member랑 Item을 가져온 다음에 어떻게 합니까?

주문 상품을 생성을 하고요. 주문 생성한 다음에

주문 저장을 하고, 저장된 식별자만 반환하도록 해놨습니다.

만약에 여기서

주문된 결과 페이지로 가려면 이 id를 반환하시는 게 필요하겠죠.

다시 원복해놓고,

여기까지 설명을 해드렸고 다음 시간에는 주문 목록을 뿌리면서 검색하고 추산하는 로직을 만들어 보겠습니다.

'스프링 > 실전! 스프링 부트와 JPA 활용1' 카테고리의 다른 글

상속관계 매핑  (0) 2024.08.17
필드와 컬럼 매핑  (0) 2024.06.07
변경 감지와 병합(merge)  (0) 2024.05.16
상품 수정  (0) 2024.05.15
상품 목록  (0) 2024.05.14