hoondev

[Spring JPA] 값 타입 컬렉션 본문

Spring JPA

[Spring JPA] 값 타입 컬렉션

hoondev3 2023. 2. 9. 13:55

값 타입 컬렉션

값 타입을 컬렉션에 담아서 사용할 수 있다.
컬렉션은 db에 매핑할때 컬렉션 용 테이블을 따로 생성해서 관리해야한다.

 

Address

@Embeddable
@Getter @Setter
public class Address {
  
  @Column(name="city") 
  private String city;
  private String street;
  private String zipcode;
  
  ...
}

 

Member

@Entity
public class Member {
  
  @Id @GeneratedValue
  private Long id;
  
  @Column(name = "username")
  private String username;
  
  @Embedded
  private Address homeAddress;

  @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<>();
  
  ...
}

 

특징

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

 

저장

Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("homeCity", "street", "10000");

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

member.getAddressHistory().add(new Address("old1", "street1", "10000"));
member.getAddressHistory().add(new Address("old2", "street2", "10000"));

em.persist(member);

member만 persist해주면 값타입들은 자동으로 persist된다. (생명주기가 member에 소속)

cascade, orphanremoval 기능을 필수로 가진다고 볼 수 있다.

그리고 값타입 컬렉션은 지연로딩을 사용한다.

 

수정

값 타입 업데이트

Member findMember = em.find(Member.class, member.getId);

Address a = findMember.getHomeAddress();
findMember.setHomeAddress(new Address("newCity", a.getStreet(), a.getZipcode()));

사이드 이펙트가 발생할 수 있는 setter를 사용하지말고 새로운 인스턴스를 생성해서 깔아끼워주자.

 

값 타입 컬렉션 업데이트

findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");

findMember.getAddressHistory().remove(new Address("old1", "street1", "10000"));
findMember.getAddressHistory().add(new Address("newCity1", "street1", "10000"));

기존에 있던 값을 삭제하고 새로운 값을 추가해야 한다.

그럼 삭제 한번 인서트 한번 쿼리가 나갈것 같지만 아니다.

변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.

 

 

값 타입 컬렉션의 제약사항

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

 

값 타입 컬렉션 대안

실무에서는 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려하자.

일대다 관계를 위한 엔티티를 만들고, 여기에서 값 타입을 사용하자.

영속성 전이(Cascade) + 고아 객체 제거를 사용해서 값 타입 컬렉션 처럼 사용하자. EX) AddressEntity

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "MEMBER_ID")
private List<AddressEntity> addressHistory = new ArrayList<>();
@Entity
@Getter @Setter
@Table(name = "ADDRESS")
public class AddressEntity {

    @Id @GeneratedValue
    private Long id;
    private Address address;

    public AddressEntity() {
    }

    public AddressEntity(String city, String street, String zipCode) {
        this.address = new Address(city, street, zipCode);
    }
}

 

정리

엔티티 타입의 특징

  • 식별자O
  • 생명 주기 관리 
  • 공유 

 

값 타입의 특징

  • 식별자X
  • 생명 주기를 엔티티에 의존
  • 공유하지 않는 것이 안전(복사해서 사용)
  • 불변 객체로 만드는 것이 안전

 

값 타입은 정말 값 타입이라 판단될 때만 사용 엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안됨 식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면 그것 은 값 타입이 아닌 엔티티

 

Reference

https://www.inflearn.com/course/ORM-JPA-Basic

Comments