본문 바로가기
Spring JPA

값 타입 - 12

by 홍굴이 2021. 7. 16.

임베디드 타입(복합 값 타입)

 

@Entity
public class Member {

    @Id
    @GeneratedValue
    private Long id;
    
    private String name;

    @Embedded
    private Address homeAddress;
    
    @Embedded
    @AttributeOverrides({
    	@AttributeOverride(name = "city", column=@Column(name = "company_city")),
        @AttributeOverrdie(name = "district", column=@Column(name = "company_district")),
        @AttributeOveerdie(name = "zipcode", column=@Column(name = "company_zipcode"))})
    private Address companyAddress;
    
}
@Embeddable
public class Address {

    @Column(name = "city")
    private String city;
    
    @Column(name = "district")
    private String district;
    
    @Embedded
    private Zipcode zipcode;

}
@Embeddable
public class Zipcode {

    @Column(name = "zip")
    private String zip;
    
    @Column(name = "plus_four")
    private String plusFour;

}

다음 코드와 같이 주소 클래스를 따로 만들어 구현하면 응집도가 높아지고 객체지향적으로 바뀔 수 있다.

  • @Embeddable : 값 타입을 정의하는 곳에 표시
  • @Embedded : 값 타입을 사용하는 곳에 표시
  • @AttributeOverride : 속성 재정의
    • 임베디드 타입에 정의한 매핑정보를 재정의할 때 사용한다.
    • 똑같은 임베디드 타입이라면 컬럼명이 중복되므로 매핑정보를 재정의할 수 있다.
  • 임베디드 타입이 null이면 매핑한 컬럼 값은 모두 null이 된다. 즉, member.setHomeAddress(null); 을 하면 연관된 컬럼 값은 모두 null이다.

값 타입과 불변 객체

임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 안된다.

member1.setHomeAddress(new Address("OldCity"));
Address address = member1.getHomeAddress();

address.setCity("NewCity");
member2.setHomeAddress(address);

member2의 주소만 "NewCity"로 변경되길 기대했지만 member1의 주소도 "NewCity"로 변경된다.

member1과 member2가 같은 address 인스턴스를 참조했기 때문에 영속성 컨텍스트는 member1과 member2 둘 다 city 속성이 변경되었다고 판단하여 각각 update SQL을 실행한다.

따라서 가장 좋은 방법은 불변 객체를 만드는 것 이다.

 

객체를 불변하게 만들면 값을 수정할 수 없으므로 부작용을 원천 차단할 수 있다.

즉, 객체를 만들 때 Setter를 만들지 않고, 생성자를 통해서만 생성할 수 있게 하면 된다.


값 타입 컬렉션

@Entity
public class Member {

    @Id
    @GeneratedValue
    private Long id;

    @Embedded
    private Address homeAddress;

    @ElementCollection
    @CollectionTable(name = "favorite_foods",
            joinColumns = @JoinColumn(name = "member_id"))
    @Column(name = "food_name")
    private Set<String> favoriteFoods = new HashSet<String>();

    @ElementCollection
    @CollectionTable(name = "address",
            joinColumns = @JoinColumn(name = "member_id"))
    private List<Address> addressHistory = new ArrayList<Address>();
}
@Embeddable
public class Address {

    @Column(name = "city")
    private String city;

    @Column(name = "district")
    private String district;

}
  • 값 타입을 하나 이상 저장하려면 컬렉션에 보관하고 @ElementCollection, @CollectionTable 어노테이션을 사용하면 된다.
  • 값 타입 컬렉션에 보관된 값 타입들은 별도의 테이블에 보관된다.
    따라서, 보관된 값 타입이 변경되면 데이터베이스에 있는 원본 데이터를 찾기 어렵다는 문제가 발생한다.
    따라서 JPA 구현체들은 값 타입 컬렉션에 변경 사항이 발생하면, 값 타입 컬렉션이 매핑된 테이블의 연관된 모든 데이터를 삭제하고, 현재 값 타입 컬렉션 객체에 있는 모든 값을 데이터베이스에 다시 저장한다.

정리

엔티티 타입의 특징

  • 식별자가 있다.
    -> 엔티티 타입은 식별자가 있고 식별자로 구분할 수 있다.
  • 생명 주기가 있다.
    -> 생성하고, 영속화하고, 소멸하는 생명 주기가 있다.
  • 공유할 수 있다.
    -> 참조 값을 공유할 수 있다.

값 타입의 특징

  • 식별자가 없다.
  • 생명 주기를 엔티티에 의존한다.
    -> 스스로 생명주기를 가지지 않고 엔티티에 의존한다. 즉, 의존하는 엔티티를 제거하면 같이 제거된다.
  • 공유하지 않는 것이 안전하다.
    -> 엔티티 타입과는 다르게 공유하지 않는 것이 안전하다. 대신에 값을 복사해서 사용해야 한다.
    -> 오직 하나의 주인만이 관리해야 한다.
    -> 불변 객체로 만드는 것이 안전하다.

'Spring JPA' 카테고리의 다른 글

객체 지향 쿼리 언어 - 14  (0) 2021.07.21
객체지향 쿼리 언어 - 13  (0) 2021.07.17
프록시와 연관관계 관리 - 11  (0) 2021.07.13
고급 매핑 - 10  (0) 2021.07.12
다양한 연관관계 매핑 - 9  (0) 2021.07.09

댓글