당니의 개발자 스토리
실전 예제 1 - 요구사항 분석과 기본 매핑 본문
실전 예제 1 - 요구사항 분석과 기본 매핑
이번에는 실전 예제 첫 시간입니다.

요구사항 분석과 기본 맵핑입니다. 이번에 저희가 처음으로 엔티티 맵핑에 대해서 배웠기 때문에 이거에 대해서 조금 더 복잡한 예제를 통해서 어떻게 맵핑 되는지 알아보겠습니다.

먼저 요구사항 분석인데요. 회원은 상품을 주문할 수 있고 주문 시 여러 상품을 선택할 수 있다. 이런 요구사항이 있구요.

기능은 회원을 관례하고 상품을 등록하고 주문할 수 있고 이런 기능들이 있는 간단한 샘플입니다. 기본적인 굉장히 단순한 쇼핑몰 이라고 보시면 될 것 같구요.
도메인 모델을 분석을 해보면,

아까 본 요구사항을 가지고 이제 뭘 만들어 볼 거에요. 회원은 여러 번 주문할 수 있다. 하나의 회원이 여러 번 주문할 수 있다. 주문과 상품의 관계. 주문할 때 여러 상품을 선택할 수 있는 거예요. 반대로 상품도 여러 번 주문될 수 있죠.
그래서 주문 상품이라는 모델을 만들어서, 다대다 관계를 일대다-다대일 관계로 풀어냅니다.
그래서 이걸 테이블로 설계하면,

MEMBER가 ORDERS랑 1대 n 관계가 되구요. 그 다음에 Item. 고객이 상품을 주문하게 되면, ORDER_ITEM 이라는 테이블에 쌓이게 됩니다. MEMBER에 보시면 기본적인 이름이랑 주소 정보가 간단하게 들어가고, 그 다음에 주문은 정말 단순하게 어떤 주문 고객이 주문했는지 주문번호 MEMBER_ID가 Foreign Key로 들어가죠. 그 다음에 주문시간, 주문의 상태. 고객이 뭐 결제했다, 배송됐다, 주문 취소했다. 이런 상태겠죠.
상품은 상품의 id랑 상품명, 가격, 재고 수량 정도의 정보가 있고요.
그 다음 중간 테이블이 있죠. 고객이 주문을 하면 ORDER_ITEM 연결 테이블을 하나 만들었고요. 여기에는 주문번호가 뭔지, 상품 id가 뭔지, 그 다음에 orderPrice, 금액이랑 몇 개 주문했는지, 수량까지 있습니다. 그러니까 하나의 주문이 여러 개 상품을 구매할 수 있는 모델인 거죠.
자 이걸 엔티티로 매핑을 하면 이 모양이 됩니다.

방금 테이블이었고 엔티티 같은 경우에는 Member가 쭉 나오고요.
Order도 memberId, 그리고 Item도 id 쭉 있고, OrderItem을 보시면 어떤 id인지 id가 직접적으로 들어가 있습니다.
이제 왜 굳이 이렇게 했냐면,

사실 이 테이블 모델링을 보고 보통 객체 모델링을 하세요. 라고 하면,

처음에는 보통 이렇게 나와요. 왜냐하면 아직 저희가 뭐 연관관계 맵핑을 안 배운 것도 있지만 보통 Order면 memberId가 들어가 있죠. 그냥 테이블이랑 똑같이 설계를 한 거 거든요. 이거에 대한 문제점이 뭔지 코드로 직접 해보면서 한번 설명을 드리겠습니다.
제가 이 예시를 그대로 한번 만들어보겠습니다.

프로젝트를 설정하는 건 3:54부터 5:40까지 보면 되고요.
자 이제 Member부터 시작해서 껍데기를 다 만들어놓고 시작하겠습니다.

이렇게 만들었구요.
이제 Member부터 요구사항에 맞춰서 쭉쭉 이제 코드를 작성해 볼게요.

이렇게 필드를 쭉쭉쭉 넣고, 여기서 식별자를 필수를 해야 되겠죠. 이 id를 직접 세팅할 건 아니고 db가 만들어주는 값을 쓴다고 가정하겠습니다. 그러면 @GeneratedValue를 해주시고 Strategy가 있는데요.

기본이 Auto에요. 그래서 Strategy를 AUTO로 하시면 되는데, 그냥 생략하면 Auto 거든요.

일단 이렇게 해둘게요.
자 그 다음에 Getter Setter 만들어 줘야죠.

쭉 만들고 참고로 Getter Setter를 꼭 다 만들 필요는 없는데 Getter는 가급적 만들어 주시는게 좋고 Setter 같은 경우에는 조금 고민할 필요가 있죠. 근데 지금은 예제니까 그냥 다 만들겠습니다. Setter를 막 만들면 아무데나 set, set 할 수 있으니까 이제 코드 추적하기 별로 좋지 않겠죠. 유지 보수성이 떨어질 수 있습니다. 그래서 가급적 생성자에서 값을 다 세팅을 하고 Setter의 사용을 최소한 하는 게 아무래도 유지 보수성이 낫겠죠.

Order는 memberId가 재미있는데, 그러니까 누가 주문했는지 알아야 되니까요. 그 다음에 LocalDateTime으로 할게요.

이제 status를 만들어주는데, 이건 주문의 상태인데

Enum 클래스로 만들겠습니다.

order, cancel 이렇게 두 가지만 만들어 두겠습니다.
자 이렇게 한 다음에 한 개씩 살펴보면,

meberId는 건들 필요가 없고, 이제 여러분이 Hibernate를 쓰시는 건 다 최신 버전일 거예요. 그래서 최신 버전에서는 LocalDateTime으로 이렇게 하면 돼요. 지금 Java 8에 들어간 LocalDate, LocalDateTime에 대해서 자동으로 맵핑을 해줘요. 그래서 따로 손을 안 대셔도 되고요.

그리고 Enum 타입 같은 경우에는 @Enumerated를 해주시고 지난 시간에 설명드렸던 것 중에서 Ordinary을 쓰면 안된다고 했죠.

String으로 해주셔야 됩니다. Ordinary를 쓰면 순서가 나중에 꼬여서 들어가면 큰 장애가 날 수 있습니다.

지금 Order 같은 경우에는 이전에 테이블을 보면 이 테이블명이 ORDERS라고 되어있죠. 왜 그러냐면 이게 DB마다 좀 다른데 이제 OrderBy 라고 DB에는 Order가 예약어로 걸려 있어요. 그래서 Order를 테이블 명으로 그대로 썼을 때 되는 DB도 있는데 안되는 DB도 있거든요. 그래서 그냥 ORDERS로 많이 씁니다.

Item도 id를 만들어주고, @Column 해서 이름을 붙여줘야 합니다. 다른 엔티티들도 다 해줘야겠죠.

그 다음에 수량이 10억 20억 넘어갈 일은 없으니까 이렇게 int 타입을 했구요.

getter, setter도 해줘야겠죠.

Member도 MEMBER_ID 하는데 지금 제가 대문자로 썼죠. 이게 회사마다 룰이 조금씩 달라요. dba 분이 가이드하는 룰이 있으니까 그 룰대로 쓰시면 되고 예전에는 대문자를 많이 썼는데

요즘에는 이렇게 또 소문자로 많이 쓰더라구요. 소문자의 언더스코어(밑줄) 방식을 많이 써요.

아무튼 지금 예제는 제가 대문자로 하겠습니다.

Order도 이렇게 써주시구요.

Order Item도 해줘야죠.

이렇게 가격을 적어주고, 수량을 count 라고 하겠습니다. 그리고나서, getter, setter를 다 해주고 이정도면 대략적으로 된 것 같죠.

이렇게 하고 제대로 되는지 한번 실행을 해봐야 되니까요. JpaMain을 만들어서 메인 메서드를 실행해볼게요.

그리고 헷갈리지 않게 이 jdbc url을 바꿔놓겠습니다.





이전에 배웠던 내용들로 jpashop2 라고 연결해놓겠습니다.
./h2.sh 실행을 한 상태로 돌려보면,

자 보시면 쭉쭉쭉 만들어졌고,

db도 Refresh 해보면 테이블도 쭉 만들어져 있죠.

이제 city 처럼 제가 따로 안 적어준 애들은 룰이 있는데,

그냥 이거랑 똑같이 간다고 보시면 돼요. 이제 그 룰이 몇 개 있는데 사실 복잡하고 외울 필요도 없고, 애매하면 그냥 다 @Column 으로 맵핑을 해주시면 되구요. 관례를 따르고 싶다 그러면 관례를 따르시면 되는데 좀 애매한 게 있어요.
왜냐면은 아무래도 DB는 보통 언더스코어가 관례로 많이 굳어져 있고 그 자바는 Camel Case 라고 하죠.

그러니까 예를 들어서 memberId는 이렇게 쓰고, db는 둘 중 하나로 이런 식으로 쓴단 말이에요. 요즘에는 대소문자를 구분하는 옵션도 있지만 옛날에는 없었기 때문에 관례로 언더스코어를 많이 써요.

암튼 그래서 좀 애매한 거는 이렇게 직접 매핑 해주시면 됩니다.

자 이제 여기 보시면 Member 같은 경우에는 MEMBER_ID가 이제 pk 로 잡힌 걸 확인하실 수 있고요. 나머지는 제가 그냥 넣은 대로 쭉쭉 들어갔습니다.

이런 메타 데이터 255가 남는 게 싫으시면, @Column을 해서 length 주셔도 되구요. 아니면은 그냥 개발할 때 운영의 db 스크립트를 반영하기 전에 이것을 좀 수정하셔서 운영의 db 스크립트를 반영하셔도 되구요. 그건 이제 여러분의 선택입니다.
근데 저는 어떤걸 선호하냐면, 가급적이면 메타 데이터를 entity 클래스에 그대로 적는 것을 선호를 해요.
예를 들어서 name의 길이가 10자면,

@Column 해서 length = 10 이걸 해두면 장점이 뭐냐면 이것만 봐도 개발자가 알 수 있거든요. 그래서 이런 제약도 그냥 저는 다 추가하는 편입니다. 안그러면 이것도 db 테이블을 다 까봐야 되잖아요.

인덱스도 마찬가지로 @Table에 unique 제약 조건을 넣을 때 unique랑

인덱스 넣을 수 있는데 저는 가급적으로는 이것도 다 넣는 편이에요.

왜냐하면 그래야 개발자들이 객체를 보고 jpql 쿼리 짤 때 사용하는 인덱스를 좀 정확하게 셀렉트 할 수 있거든요. 나중에 jpql 같은 거에 결국 where 문을 짜는데 그걸 또 테이블 보고 왔다 갔다 하는 것보다는 entity 보고 '아 얘는 이거 이거 인덱스 걸리니까 이거 쓰면 되겠구나' 해서 이제 딱 싱크를 맞추는 거죠. 그래서 저는 가급적이면 그 이 맵핑을 다 적는 편입니다.

그래서 저희 회사 같은 경우에는 소문자가 룰이거든요. 그래서 뭐 그런거 같은 경우에는 관례로 그대로 가야되면 따로 수정하지 않는 편이에요.

물론 여기서 length나, 아까 말씀드린 대로 이름을 수정해야 되면 수정해야 되죠.
또 재밌는 게 jpa 하이버네트를 순수하게 쓰시면,

이게 이름이 거의 그대로 나가거든요.

뭐 OrderDate 이런거 있잖아요. 결국엔 여러분이 실제 애플리케이션 운영해서 개발하실 때는 SpringBoot를 쓸텐데 그 SpringBoot를 쓰면 SpringBoot가 Hibernate 설정에서 이 관례를 내가 원하는대로 오버라이딩 할 수 있어요.

원래대로라면 기본이 이렇게 그대로 나가죠. 그런데 보통 dba 들이 원하시는 건 이게 아니고,

아까 말씀드렸듯이 이거거나 이거거든요.

그니까 지금 저희 회사 같은 경우에는 order_date가 관례인데 회사마다 다 달라요. 하이버네이트나 JPA를 직접 쓸 때는 관례로 하는 게 기본 값인데 이게 재미난 게 db가 하도 이렇게 많이 쓰니까 스프링 부트에서는 하이버네이트 JPA를 걸어서 올리시면 부트가 기본 설정을

이걸로 가져갑니다. 재밌죠? 자바의 Camel Case를 읽어서 order_date로 바꿔주는 설정을 기본으로 가져갑니다.

이런 Camel을 언더스코어 소문자로 딱 가져가거든요.
'맵핑 정보가 다 눈에 보였으면 좋겠어!' 하고 이제 @Column 해서 일일이 적으시는 분들도 있고 약간 저는 스타일이라고 봐요.

그런데 이상한 게 있어요. 주문을 했는데 이제부터 핵심입니다.

제가 이 코드를 사용한다고 과정을 해볼게요.

그러면 이 Order를 뭔가 찾아왔어요. 수도 코드처럼 짜보면,

em.find 해서 주문을 찾아왔어요. 그럼 이 주문을 한 Member를 찾고 싶어요.

그래서 getMemberId를 얻어와요. 그래서 memberId를 찾은 다음에 또 어떻게 해야 합니까? 또 em.find 해서 memberId 찾은 걸 또 넣어서 Member를 찾아야 되겠죠. 이게 뭔가 좀 객체 지향스럽지 않다는 느낌 오시죠?
좀 더 객체 지향적인 코드는

Member를 아예 넣어서, 주문한 Member가 누군지를 쉽게 찾아와야 되겠죠.

getter, setter도 넣어주고

그럼 이렇게 찾아야 되겠죠.

Order에서 order.getMember 해서 id가 아니라 Member를 바로 끄집어낼 수 있어야 되겠죠.

그러면 여기 Member에서 바로바로 값을 쓸 수 있겠죠.

이게 사실은 객체는 객체 그래프로 뭔가 참조로 쭉쭉쭉 찾아갈 수 있어야 되거든요. 근데 식별자로 받고 있으면 그게 끊겨 들어가는 거죠.

자 그래서 이런 방식의 설계는 사실은 좀 객체 지향스럽지 않죠. 우리가 기존에 보통 이런 설계를 뭐라고 하냐면 관계형 DB에 맞춘 설계라고 보시면 돼요. 객체보다는 관계형 DB를 객체에 맞춘 거죠. 관계형 DB에 있는 걸 그대로, 객체를 관계형 DB 쪽에 맞춰서 설계를 한 거죠.

자 그러면 이런 설계의 문제점은 뭐냐면,

현재 방식은 객체 설계를 테이블 설계에 맞춘 방식입니다. 테이블의 외래키를 객체에 그대로 가져온단 말이에요. 그냥 참조 값을 가져와야 되는데 그게 아니라서 객체 그래프 탐색이 불가능하죠. order.getMember 해서 바로 찾을 수 있어야 되는데, 그게 아니고 식별자만 가지고 오니까 참조가 없으므로 사실 UML도 잘못된 거예요.

이게 보시면 지금 id만 가지고 있는 거잖아요. 참조가 다 끊기죠.
그럼 이제 뭘 해야 되냐? 그래서 이제 다음 시간에 연관관계 맵핑에 대해서 배웁니다.

이 Order에서 이 Member를 어떻게 할 거냐?

이런식으로 id를 가지는 게 아니라 진짜 Member를 가지는 거죠.
그게 이제 다음 시간에 알아볼 객체지향 설계, 연관관계 맵핑에 대한 내용입니다.
'스프링 > 자바 ORM 표준 JPA 프로그래밍 - 기본편' 카테고리의 다른 글
| 양방향 연관관계와 연관관계의 주인 1- 기본 (0) | 2024.06.10 |
|---|---|
| 단방향 연관관계 (0) | 2024.06.10 |
| 기본 키 매핑 (0) | 2024.06.08 |
| 데이터베이스 스키마 자동 생성 (0) | 2024.05.27 |
| 객체와 테이블 매핑 (0) | 2024.05.27 |