개발일지

@ElementCollections 사용기

gilssang97 2022. 1. 1. 10:28

값 타입 컬렉션 사용기 (feat. @ElementColletion)

이번에 값 타입 컬렉션을 사용하게 되었다.

값 타입 컬렉션이란 컬렉션에 값 타입을 담는 것을 의미하는데 값 타입이라는 것이 정확히 무슨 뜻인지에 대해서 알아보자.

값 타입은 흔히 엔티티와 많이 비교된다.

먼저, 우리에게 익숙한 엔티티에 대해서 먼저 알아보자.

엔티티는 우리가 흔히 @Entity로 정의하는 객체이며 @Id라는 어노테이션으로 정의한 PK(식별자)를 통해 계속적으로 추적이 가능하다.

하지만, 값 타입은 식별자라는 개념이 존재하지 않아 추적이 어렵다. 값 타입은 흔히 우리가 사용하는 Integer, String과 같은 자바의 원시 타입과 같은 존재로 우리가 흔히 사용하는 객체와 달리 공유에 안전하고 사이드 이펙트가 발생하지 않는다.

저번 포스트에서 언급한 임베디드 타입의 경우에는 구성 요소 자체가 값 타입으로 이루어져 있긴 하지만 임베디드 타입 그 자체가 불변 객체가 아니기에 위의 장점들을 포함하고 있지 않는다.

이처럼 값 타입은 참조 타입에 비해 많은 장점을 제공하기에 이펙티브 자바에서도 불변 객체로 만들어라라는 팁을 제공해주곤 한다.

이제 값 타입에 대한 이해를 기반으로 값 타입 컬렉션에 대해 알아보자.

값 타입 컬렉션은 말 그대로 값 타입을 저장하는 것이다. (ex) List)

값 타입 컬렉션은 변경사항이 생긴다면 모두를 지웠다가 다시 만든다.

자바에서의 String을 생각해보자.

다음과 같이 단순히 자바에서 Hello라는 String 인스턴스를 선언했다.

String str = "Hello"

이를 "Bye"라고 바꾸면 str 인스턴스는 동일한 참조를 가르킬까?

그렇지 않다.

새로운 "Bye"라는 객체를 String Constant Pool에 생성해 이를 가르키게 된다.

이와 비슷한 개념이다.

값 타입 컬렉션에 변경이 생기면 기존 것을 지우고 새로 컬렉션을 만든 것을 가르키게 된다.

위와 같은 특징을 이루기 위해 꼭 불변 객체로 만들어 주는 좋다. (참조 타입으로 컬렉션을 구성할 경우, 복사 후 변경과 같은 사항에서 안전하지 못하다.)

또한, 값 타입 컬렉션은 위와 같은 특징을 가지며 생명주기를 엔티티에 의존하기에 이전 포스트에서 설명한 Cascade와 동일한 효과를 누린다.

하지만 위에서 설명했듯이 값 타입은 추적이 불가능하다. 즉, 제거되거나 변경되면 더이상 추적이 불가능하다는 뜻이다. (새로운 객체로 변경된다.)

그래서 우리는 값 타입 컬렉션의 사용에 신중해야 한다.

이번에 나는 값 타입 컬렉션을 사용할만한 부분을 찾아 이를 적용해보려고 했다.

프로젝트를 진행하는 중 스터디를 생성하는데 있어 "태그"가 필요하다는 사실을 알게되었다.

해당 스터디가 어떤 분야에 관련있는지에 대한 명확한 정보를 단순 제목으로만 제공해줄 수 없기에 태그를 추가하여 이에 대한 정보를 보충하고자 하였다.

요즘 사용하는 인스타그램 등 다양한 플랫폼에서는 단일 태그만 제공하는 것이 아니라, 여러 개의 태그를 제공할 수 있게 하였다.

나도 위처럼 다양한 태그를 제공할 수 있게 만들기 위해 컬렉션을 사용하기로 하였다.

이제 나는 이 태그라는 객체에 대해 엔티티로 사용할지 단순 값 타입(String)으로 사용할 지에 대한 고민이 생겼다.

태그라는 값은 우리가 지속적으로 추적하며 사용해야할 값인가라는 것에 대해 단순 태그를 추가하고 제거하는 부분만 생각한다면 이는 값 타입 컬렉션으로 유지하며 사용할 수 있다고 생각하여 이를 직접 적용해보기로 하였다.

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(of = "id")
public class Study extends EntityDate {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "study_id")
    private Long id;
    private String title;
    private String content;
    private String profileImgUrl;
    private String department;
    private Long headCount;
    @Embedded
    private StudyOptions studyOptions;
    @Embedded
    private Schedule schedule;
    @ElementCollection
    @CollectionTable(name = "STUDY_TAG", joinColumns = @JoinColumn(name = "study_id"))
    @Column(name = "TAG_NAME")
    private List<String> tags = new ArrayList<>();

    ...

}

위와 같이 태그를 저장하는 tags에 값 타입 컬렉션을 구현할 수 있도록 해주는 @ElementCollection 어노테이션을 사용하였다.

그리고, @CollectionTable 어노테이션을 통해 테이블의 이름은 어떤 것으로하고 조인을 할 때 사용할 컬럼을 지정해주었다.

스터디의 경우 PK(study_id)로 지정해주어 이를 로드할 수 있도록 하였다.

그리고 실제 태그가 저장되는 컬럼을 @Column 어노테이션을 통해 설정해주었다.

이제 실제로 이러한 값 타입 컬렉션을 제공해주는 테이블이 어떻게 생성되었는지 확인해보자.

위와 같이 조인 컬럼과 저장할 컬럼 두 개로 이루어진 테이블이 생성되어있는 것을 확인할 수 있었고 이러한 부분을 매핑해주는 사실을 알 수 있었다.

하지만 이러한 값 타입은 엔티티와 혼동되어 사용되면 안된다.

그러기에 이러한 사용에 있어서는 아직 조금 더 고민해봐야할 여지가 존재하는 것 같다.