[Spring] 임베디드 타입

JJoSuk·2023년 7월 28일
0

임베디드 타입이란?

임베디드 타입

  • 새로운 값 타입을 직접 정의할 수 있음
  • JPA는 임베디드 타입(embedded type) 이라 함
  • 주로 기본 값 타입을 모아서 만들어서 복합 값 타입이라고도 한다.
  • int, String 과 같은 값 타입

예시) 엠베디드 타입을 활용하여 Member 테이블을 만들어보자.

  • id
  • name
  • startDate
  • endDate
  • city
  • street
  • zipcode

위 테이블을

  • startDate + endDate = workPeriod
  • city + street + zipcode = homeAddress

임베디드 타입 적용 후 Member 테이블이 간소화 할 수 있다.

임베디드 타입의 장점

  • 재사용
  • 높은 응집도
  • Period.isWork() 처럼 해당 값 타입만 사용하는 의미 있는 메서드를 만들 수 있다.
  • 임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티티에 생명주기를 의존한다.

임베디드 타입과 테이블 매핑

  • 임베디드 타입은 엔티티의 값일 뿐
  • 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다.
  • 객체와 테이블을 아주 세밀하게(find-grained) 매핑하는 것이 가능
  • 잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많다.

참고) 임베디드 타입과 null

  • 임베디드 타입의 값이 null 이면 매핑한 컬럼 값은 모두 null

값 타입 공유 참조

  • 임베디드 타입 같은 값 타입을 여러 엔티티에서 공유가 가능하지만 부작용이 있어서 위험하다.
  • 객체 타입을 수정할 수 없게 만들면 해결된다.(setter 제거)
  • 값 타입을 불변 객체로 설계
  • 결론 생성자로만 만들고 수정자를 만들지만 않으면 된다.
  • 참고) Integer, String 은 자바에서 제공하는 대표적인 불변객체 다.

값 타입의 비교

  • 값 타입 : 인스턴스가 달라도 그 안에 값이 같으면 같은 것으로 봐야한다.
  • 값을 만약에 비교해야 하는 상황이 있을 경우,
    • 동등성(equivalence) 비교 : 인스턴스의 값을 비교, equals() 사용

값 타입 컬렉션

  • 값 타입을 하나 이상 저장할 때 사용
  • @ElementCollection, @CollectionTable 사용
  • 데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.
  • 컬렉션을 저장하기 위한 별도의 테이블이 필요하다.

@ElementCollection
@CollectionTable(name = "FAVORITE_FOOD", joinColumns =
     @JoinColumn(name = "MEMBER_ID"))
@Column(name = "FOOD_NAME")
private Set<String> favoriteFoods = new HashSet<>();

@ElementCollection
@CollectionTable(name = "ADDRESS", joinColumns =
     @JoinColumn(name = "MEMBER_ID"))
private List<Address> addressesHistory = new ArrayList<>();

값 타입 컬렉션 사용

  • 값 타입 저장 예제
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("homeCity", "street", "10000"));

member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("피자");
member.getFavoriteFoods().add("족발");

member.getAddressesHistory().add(new Address("old1", "street", "10000"));
member.getAddressesHistory().add(new Address("old2", "street", "10000"));

em.persist(member);

tx.commit();
  • 값 타입 조회 예제
Member findMember = em.find(Member.class, member.getId());

List<Address> addressesHistory = findMember.getAddressesHistory();
for (Address address : addressesHistory) {
    System.out.println("address.getCity() = " + address.getCity());
}

Set<String> favoriteFoods = findMember.getFavoriteFoods();
for (String favoriteFood : favoriteFoods) {
    System.out.println("favoriteFood = " + favoriteFood);
}

tx.commit();
  • 가입 타입 수정 예제
Member findMember1 = em.find(Member.class, member.getId());

// homeCity -> newCity
// findMember.getHomeAddress().setCity("newCity"); 잘못된 방법
Address a = findMember1.getHomeAddress();
findMember1.setHomeAddress(new Address(a.getCity(), a.getStreet(), a.getZipcode()));

// 치킨 -> 한식
findMember1.getFavoriteFoods().remove("치킨");
findMember1.getFavoriteFoods().add("햄버거");

findMember1.getAddressesHistory().remove(new Address("old1", "street", "10000"));
findMember1.getAddressesHistory().add(new Address("newCity1", "street", "10000"));

tx.commit();

참고) 값 타입 컬렉션은 영속성 전에(Casecade) + 고아 객체 제거 기능을 필수로 가진다고 볼 수 있다.


값 타입 컬렉션의 제약사항

  • 값 타입은 엔티티와 다르게 식별자 개념이 없다.
  • 값은 변경하면 추적이 어렵다.
  • 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
  • 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야 한다 = null 입력x 중복 저장x

값 타입 컬렉션 대안

  • 실무에서는 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려
  • 일대다 관계를 위한 엔티티를 만들고, 여기에서 값 타입을 사용
  • 영속성 전이(Casecade) + 고아 객체 제거를 사용해서 값 타입 컬렉션 처럼 사용
  • 예) AddressEntity
profile
안녕하세요

0개의 댓글

Powered by GraphCDN, the GraphQL CDN