티스토리 뷰

Entity에서 일급 컬렉션을?

최근 프로젝트를 진행하던 중 두 Entity를 양방향으로 묶고, 로직을 추가하던 중에 @OneToMany 부분의 List를 가공해야 하는 일이 생겼습니다. 컬렉션을 가공할 일이 생기면 가장 먼저 떠오르는 것은 일급 컬렉션인데요. 지금까지 그럴 일이 없어서인지 한번도 엔티티 안에서 일급 컬렉션을 사용해 본 적이 없었다는 것을 깨달았습니다.

도메인 레이어를 풍성하게

상황은 다음과 같았습니다.

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Orders extends BaseEntity {

    // ...

    @OneToMany(mappedBy = "orders", cascade = CascadeType.PERSIST, orphanRemoval = true)
    private List<OrderDetail> orderDetails = new ArrayList<>();

    // ...
}

위 예시의 주문을 의미하는 Orders 엔티티와 주문 상세를 의미하는 OrderDetail 엔티티는 서로 관계가 긴밀하다고 판단을 하여 양방향 매핑으로 묶었습니다. (양방향 매핑은 꼭 필요한 경우가 아니면 자제하는 것이 좋습니다. 관리해야 할 포인트가 늘어나고, 사실상 단방향 매핑만으로 모든 관계를 구현할 수 있기 때문입니다.)

 

요구 사항을 구현하다 보니 Orders의 orderDetails 리스트를 stream 등으로 가공해야 할 일이 생겼습니다. 물론 해당 엔티티를 담당하는 서비스 레이어에서 담당해도 되겠지만, 도메인 레이어를 풍성하게 만드는 것이 좋기 때문에 해당 엔티티 내부에 넣어야겠다고 생각했습니다.

 

컬렉션의 가공, 하면 떠오르는 것이 바로 일급 컬렉션입니다. 일급 컬렉션은 내부에 컬렉션을 가지고 있는 객체인데요. 다양한 장점을 가지고 있기 때문에 OOP를 지향하기 위해서는 반드시 익혀야 할 개념입니다. 일급 컬렉션의 장점에 대해서는 이 글을 참고하시면 좋습니다.

다만 엔티티 내부의 리스트를 일급 컬렉션으로 묶어본 적이 없었어서, 방법이 없나 찾아보았는데요. 생각보다 간단했습니다.

답은 @Embeddable!

@Embeddable은 엔티티의 여러 필드들을 하나로 묶어서 관리하고 싶을 때 사용합니다. @Embeddable은 실제로는 하나의 객체지만, DB의 테이블에서는 해당 객체 내부에 있는 필드들이 풀어져서 만들어집니다. @Embeddable의 자바 주석을 따라 들어가 보시면 친절한 예시가 나와있습니다. 다음과 같이 객체에서는 주소(Address)라는 이름으로 street, city, state, zipcode를 묶어서 관리하고 싶을 때 사용하면 됩니다. zipcode 자체도 또 @Embeddable 객체네요.

 

@Embeddable

다음과 같이 @Embeddable을 사용해 쉽게 해결할 수 있었습니다.

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Orders extends BaseEntity {

    // ...

    // @Embedded
    private OrderDetails orderDetails = new OrderDetails();

    // ...
}

@Getter
@NoArgsConstructor(access = AccessLevel.PUBLIC)
@Embeddable
public class OrderDetails {

    @OneToMany(mappedBy = "orders", cascade = CascadeType.PERSIST, orphanRemoval = true)
    private List<OrderDetail> details = new ArrayList<>();

    // ...
}

OrderDetails라는 복수 형태의 이름으로 일급 컬렉션을 묶고, @Embeddable을 사용했습니다. @Embeddable은 해당 클래스 위에 선언합니다. 혹은 Orders의 orderDetails 필드에 @Embedded라고 선언해도 됩니다. 둘 다 명시해줘도 되고, 둘 중 하나만 선언해주어도 동작합니다. 저는 개인적으로 해당 클래스에 @Embeddable만 선언하는 것을 선호합니다. OrderDetails 클래스가 일반 엔티티가 아니라 엔티티에 내장된 객체라는 것을 명시해줄 수 있기 때문입니다.

 

프로젝트 중에 간단하지만 새로운 사실을 알게 되어 빠르게 정리해 보았습니다. 잘 알고 있다고 생각했던 두 개념이 합쳐질 수 있다는 사실은 사소하지만 그냥 넘어가서는 안된다고 생각합니다. 일급 컬렉션과 @Embeddable 모두 적절한 곳에 잘 쓰면 객체지향적인 설계를 할 수 있는 중요한 개념들이기 때문에 익혀두시면 좋습니다. 읽어주셔서 감사합니다! :)

댓글
  • 프로필사진 Favicon of https://dublin-java.tistory.com bedi 웹 환경에서 일급컬렉션 사용할 생각을 해본적이 없었는데 이런 생각하신 모습이 멋집니다! 2020.01.12 22:51 신고
  • 프로필사진 Favicon of https://www.wbluke.com wbluke 이렇게 적용했을 때 다른 이슈가 없을지는 아직 잘 모르겠네요ㅎㅎ 추가이슈있으면 공유드리겠습니당 2020.01.13 10:33 신고
  • 프로필사진 Favicon of https://myeongjae.tistory.com Myeongjae Kim @Embeddable을 자주 쓰면서도 연관관계 매핑과 섞어서 적용해볼 생각은 한 번도 못해봤었습니다. 좋은 아이디어 얻어갑니다~~ 2020.02.06 12:44 신고
  • 프로필사진 Favicon of https://www.wbluke.com wbluke 넵넵 저도 처음 시도해봤는데 혹여나 다른 이슈있으면 공유해주세요~~ 늘 감사합니다 명재님 :) 2020.02.06 15:30 신고
  • 프로필사진 질문자 안녕하세요. 작성하신 글에대해 두 가지 질문이있는데요.
    1. OrderDetail 클래스는 엔티티 클래스가 아닌 그냥 Java Class인가요?
    2. 일급 컬렉션 사용시 Order에 종속적인 OrderDetail에 대해서 일급컬렉션을 사용하였는데 OrderDetail이 아닌 Devlivery(배송)과 같은 종속적이지 않으며 List로 표현해야할 클래스가 있으면 일급컬렉션을 사용해야 할까요?
    2020.03.03 09:21
  • 프로필사진 Favicon of https://www.wbluke.com wbluke 안녕하세요ㅎㅎ 답변 드리겠습니다 :)

    1. OrderDetail도 마찬가지로 엔티티입니다.

    2. 일급컬렉션의 적용 유무가 종속 / 비종속에 걸려있는문제라기보다는 일급컬렉션의 장점이 드러나는 곳에 적용해야 한다고 생각합니다.

    저는 List<OrderDetail> 이라는 데이터에 대해 가공 혹은 validation 로직이 필요했고, 해당 List를 가지고 있는 Order에 녹여낼 수도 있었겠지만, 일급컬렉션을 적용해보면 조금 더 깔끔해지겠다는 생각에서 위와 같이 적용하였습니다ㅎㅎ

    컬렉션 형태의 자료구조가 있다고 해서 무조건 일급컬렉션을 적용할 필요는 없습니다. 오히려 오버엔지니어링이 될 수도 있습니다 :)
    예를 들어주신 Delivery 같이 Order와 종속적인 관계가 없는 엔티티라면 (상황에 따라 다르겠지만) 아마도 높은 확률로 일급컬렉션이 필요한 로직은 없을 것 같다고 생각이 드네요 :)
    2020.03.03 12:27 신고
  • 프로필사진 질문자 친절한 답변 감사합니다!ㅎㅎ
    배워갑니다~
    2020.03.03 13:49
댓글쓰기 폼