Sigrid Jin
4 min readJan 6, 2024

--

토비님 남기신 커멘트:

@ManyToOne이 lazy로 걸려있을 때 조회를 하면 DB 조회에선 fk id 값만 읽어와서 프록시 오브젝트로 넣어준다. 이게 Long이나 String 타입의 id 값으로 직접 할당하는 것에 비해서 얼마나 무거울까. Fetch join으로 DB 조회 최적화를 할 수 있고, 필요할 때 자연스러운 네비게이션이 가능하지만, 룰에 따라 내부 경계를 넘어설 때는 id 값만 꺼내서 전달하도록 만드는 것도 얼마든지 가능하다면.

나는 아직도 타입에 안전한 엔티티 대신 타입 시스템의 도움을 받을 수도 없는 Long/String 따위를 주고 받는 코드가 얼마나 더 나은지 모르겠다. 번거롭지만 ID 클래스를 쓰거나, value class를 사용한다면 모를까.

---

타입의 도움을 받는 예는 이런 겁니다.

Order - (many to one) - Member 구조의 엔티티/테이블이 있다고 해보죠.

연관관계를 엔티티 타입으로 가지고 있는 경우라면 Order안에 Member member; 값은 프로퍼티가 있을 것이고, FK ID를 직접 가지고 있다면 Long memberId; 로 되어있겠죠.

지금 Order 타입의 order 변수를 가지고 있는데, 회원 정보를 넘겨서 주문한 사람의 포인트를 업데이트 해줘야 하는 경우가 있다면, PointService에 있는 updatePoint(회원)라는 메소드를 호출할 겁니다.

이때 order가 Member 타입의 member를 가지고 있다면 이 메소드는 updatePoint(Member member)로 정의되어있고, updatePoint(order.member)라고 넣으면 됩니다. 이 메소드에 Product나 Category 같은 엔티티 타입을 넣으면 타입이 일치하지 않으니 컴파일 에러가 나겠죠.

그런데 order에서 Long 타입의 memberId를 가지고 있게 해놨다면, updatePoint(Long memberId)라고 만들어둘 것 같습니다. 그런데 실수로 updatePoint(order.memberId) 대신 updatePoint(order.sellerId)를 넣어도 문제없이 컴파일 됩니다. Seller 테이블의 id도 Long이고 이것도 Order에 Long 타입으로 가지고 있는데, 실수로 잘못 전달했을 때 컴파일러는 아무런 체크를 해줄 수 없겠죠.

OrderRepository에 findByMember()라고 만들고 JPQL에 where o.member = :member라고 해서 Member 타입을 넘기는 경우와 o.member.id = :memberId라고 해서 Long 타입을 넘기는 경우에도 실수로 다른 종류의 ID값을 넘겨도, 컴파일도 잘 되고, 실행해도 검색 결과가 안 나오거나 다른 걸 가져올 뿐이지 에러도 나지 않을 겁니다.

제가 말하는 타입 시스템에 의한 보호란 이런 걸 얘기하는 겁니다. PK/FK를 Long 타입 대신 UUID로 정의되는 String을 써도 마찬가지입니다.

이런 실수를 막을 수 있는 방법은 의존관계를 엔티티 타입으로 직접 가지고 있거나, 각 ID를 내부에 Long만 가지고 있는 ID 클래스로 선언하는 방법이 있습니다. MemberId라는 ID용 클래스를 만들어 @Id로 선언해주면 되긴합니다. OrderId 클래스도 만들고.. 이걸 선호하는 분들도 있습니다. 그러면 FK에 해당하는 값도 Long 타입 대신 MemberId 타입, OrderId 타입으로 가지고 있을 겁니다. 이 경우 내부에 값은 오직 ID만 하나 가지고 있는 프록시로 만들어지는 Member와 비교해서 "무겁다"고 느껴지는 점에서 별 차이가 없으 보이기도 합니다.

말씀하신 대로 여러가지 매핑 방법이 있고, 매번 연관 오브젝트를 직접 가지는 대신 ID 값으 사용하는 것이 필요할 때도 있고, 그걸 선택해서 선호하는 분들도 있다고 알고 있습니다. 저도 필요할 때 사용합니다. 다만 그 선택도 트레이드 오프가 있고, 상황에 따라 그 장단점을 따졌을 때 적절한, 팀에서 합의된 방식을 선택하면 충분하다고 봅니다. 하지만 그 설명이 이해가 안 되는 경우도 가끔 있긴해서 글을 써봤습니다. 성능이라거나 무겁다거나하는 것들이 정말 그런가 하는 거죠. 물론 그 외의 이유도 여러가지 알고 있어서 그게 전부는 아니라고 생각합니다만.

저 같은 경우는 같은 DB를 사용한다면 편리한 join 작성등을 위해서 의존관계를 대부분 맺지만, 모듈 경계를 넘을 때는 ID로 전달한다는 룰을 적용하는 편입니다. id 값을 꺼내는 건 간단한 일이죠. 또, id 값으로 넘길 때 실수를 막기 위해 코틀린의 value class를 이용하기도 합니다.

--

--

No responses yet