JPA. 임베디드 값 타입

무지성개발자·2024년 2월 21일
0

JPA

목록 보기
10/12

값 타입

JPA에서 말하는 값 타입은 상당히 헷갈리는 단어지만 결론은 값으로 사용되는 것을 말하며 객체 자체를 값 이라고 정의할 수 있다. 값 타입은 반드시 불변성을 지니도록 해야한다.

특징

  • 값 타입은 공유되면 안된다.
    • 임베디드 값 타입 섹션 예시 참고.
  • 값 타입은 변경시 전체 변경된다.
    • 임베디드 값 타입 섹션 예시 참고.
  • 동등성으로 비교해야한다.
  • 객체 값 타입은 생성자로 값을 부여하고 setter는 정의하지 않는 것으로 불변성을 지켜야한다.
  • 사용하는 엔티티와 같은 라이프 사이클을 가짐.
  • 값이 변경되면 추적 불가능.

기본값 타입

int, double과 같은 기본 타입 또는 Integer, String과 같은 불변성 참조 타입이 기본값 타입에 속한다.

엔티티의 속성 값들이라고 생각하면 된다.

기본타입은 특징이 값의 복사를 통해 자동으로 공유가 불가능 하지만 참조 타입의 경우 주소 값을 공유하게 되어 한 쪽에서 변경할 경우 공유한 모든 곳도 변경되니 반드시 불변객체로 사용해야함.

임베디드 타입

JPA에서 자주 사용 되는 컬럼들을 묶어 하나의 클래스로 관리하는데 객체를 값으로 사용하므로 불변성을 위해 setter메소드는 만들지 않는 것을 권장한다.

엔티티의 속성들 중 공통되고 자주 사용되는 것 들을 클래스로 묶어서 참조해서 사용하는 값 이라고 생각하면 된다.

예시

@Entity
public class Member {
	...
	@Embedded
	private Address address;
    ...
}

@Embeddable
public class Address {
    @Column(length = 10)
    private String city;

    @Column(length = 5)
    private String street;

    @Column(length = 10)
    private String zipcode;
}

주소 값으로 묶을 수 있고 다른 곳에서도 활용이 가능할 것 같은 값 들은 별도의 클래스로 관리하여 값 타입으로 사용 할 수 있으며 임베디드 값 타입이라고 한다.

    create table Member (
        MEMBER_ID bigint not null,
        ...
        city varchar(255),
        street varchar(255),
        zipcode varchar(255),
        ...
        primary key (MEMBER_ID)
    )

임베디드 값 타입을 사용하고 로그를 확인해보면 별도의 테이블이 생성 되지 않고 임베디드로 사용한 엔티티 객체의 테이블의 컬럼 값으로 사용되는 걸 확인 할 수 있다.

특징

  • 엔티티가 아니기 때문에 별도의 테이블과 맵핑 되는 것이 아님.
  • 기본 생성자 필수.
  • 재사용성과 높은 응집도
    • 임베디드로 사용 중 인 엔티티에 공통 메서드 정의 가능.

임베디드 값 타입으로 보는 값 타입 예시

  • 값 타입은 왜 공유되면 안되며 불변성이 있어야하는 지.
// aMember와 bMember가 한 집에 산다고 가정하면 아래처럼 사용 할 수 있다. 
Address address = new Address("서울", "강남로", "00011");
Member aMember = new Member(..., address, ...);
Member bMember = new Member(..., address, ...);

// bMember가 서울에서 경기도로 이사 간다고 가정하면
bMember.getAddress().setCity("경기도");
 
// aMember의 집주소도 바뀐 걸 확인가능. call by reference
System.out.println("집주소 : " + a.getAddresss().getCity()); //경기도

자바의 특징상 참조 객체를 공유하는 것을 막을 방법은 없다. 때문에 위와 같은 경우 값 타입을 공유하면 다른 곳에도 변경이 되는 사이드 이팩트가 생기는 문제가 생긴다.

예시와 같은 문제를 막기위해 값 타입을 공유하지 않고 bMember는 new Address()로 새로운 값 타입을 사용하는 것을 권장하며 만약 값을 공유했다 하더라도 setter를 정의 하지 않는 방법으로 setCity와 같은 변경은 막는 것을 권장한다.

권장 코드

// aMember와 bMember가 한 집에 살아도 값을 각각 정의
Member aMember = new Member(..., new Address("서울", "강남로", "00011"), ...);
Member bMember = new Member(..., new Address("서울", "강남로", "00011"), ...);

// bMember가 서울에서 경기도로 이사 간다고 가정하면
// bMember.getAddress().setCity("경기도"); setter정의 X
bMember.setAddress(new Address("경기도", "성남시", "00012"))
 
// aMember의 집주소도 바뀐 걸 확인가능. call by reference
System.out.println("집주소 : " + a.getAddresss().getCity()); //서울

컬렉션 값 타입

컬렉션 값 타입은 Set<String> foods, List<Address> addressHistory와 같이 값 타입을 컬렉션으로 사용하는 것을 말한다.

실무에서는 사용비중에 높지 않으며 컬렉션 값 타입 대신 OneToMany 관계의 엔티티를 만들어 사용하는 것을 우선 고려하는 것을 권장한다.

특징

  • 값 타입을 하나 이상 사용할 때 사용.
  • @ElementCollection
    • List<String>의 경우 사용
  • @CollectionTable
    • List<Address>의 경우 사용
  • 컬렉션 값 타입은 매핑되는 테이블이 생성된다.
    • 사용하는 엔티티에 리스트로 관리할 수 없으니 테이블로 따로 관리하는 느낌.
  • 지연로딩 사용 됨.

컬렉션 값 타입 대안

실무에선 일대다 관계의 엔티티 사용을 우선 고려하는 것을 권장하며 영속성 전이 + 고아객체 제거를 사용하여 컬렉션 값 타입과 같은 효과를 낼 수 있음. 또한 엔티티로 사용하면 변경사항이나 히스토리를 추적 할 수 있는 이점도 있음.

하지만 정말 단순하거나 변경사항에 관해 추적이 필요가 없다면 컬렉션 값 타입 사용을 고려할 수 있다.

결론

컬렉션 값 타입보단 엔티티 사용을 고려하며, 값 타입을 사용할시 값을 공유하지 않도록 주의 하는 것이 좋다.

또한 MappedSuperclass와 임베디드 값 타입은 비슷해 보이지만 OOP의 상속과 위임과 같은 차이가 있다는걸 인지하는 것이 좋다.

profile
no-intelli 개발자 입니다. 그래도 intellij는 씁니다.

0개의 댓글