값 타입 중 컬렉션 타입에 대해 이해한다.

인프런 강의 참고

Goal

  1. 기본값 타입
  2. 임베디드 타입 (복합 값 타입)
    • 값 타입과 불변 객체
    • 값 타입의 비교
  3. 값 타입 컬렉션

JPA의 최상위 데이터 타입 분류 (2가지)

1. 엔티티 타입

2. 값 타입

구체적인 데이터 타입 분류 (3가지)

  1. 기본값 타입
    • 자바 기본 타입 (int, double)
    • 래퍼 클래스 (Integer, Long)
    • String
  2. 임베디드 타입 (embedded type, 복합 값 타입)
    • JPA에서 정의해서 사용해야 한다.
    • e.g. 좌표의 경우, Position Class
  3. 컬렉션 값 타입 (collection value type)
    • 마찬가지로 JPA에서 정의해서 사용해야 한다.
    • 컬렉션에 기본값 또는 임베디드 타입을 넣은 형태이다.

기본값 타입과 임베디드 타입 참고 POST

3. 값 타입 컬렉션(collection value type)

public class Member {
    @Id
    private Long id;
    
    private Set<String> favoriteFoods;
    private List<Address> addressHistory;
}

구현 예시

@Entity
public class Member {
    ...
    @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> addressHistory = 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.getAddressHistory().add(new Address("old1", "street1", "10001"));
member.getAddressHistory().add(new Address("old2", "street2", "10002"));

em.persist(member);

tx.commit();

컬렉션 값 타입 조회

// ... 위와 동일한 코드 

em.flush();
em.clear();

System.out.println("============ START ============");
Member findMember = em.find(Member.class, member.getId());
// em.persist(member);

tx.commit();

컬렉션 값 타입 수정

  1. 컬렉션 값 타입 수정 예시1 - Set<String> 수정
     System.out.println("============ START ============");
     Member findMember = em.find(Member.class, member.getId());
     // 치킨 -> 한식 
     findMember.getFavoriteFoods().remove("치킨");
     findMember.getFavoriteFoods().add("한식");
    
    • String은 불변 객체 이므로 삭제하고, 다시 리스트에 넣어준다.
    • String 자체가 값 타입이므로 업데이트를 할 수가 없다. 위와 마찬가지로 통으로 갈아 끼워야 한다.
    • 컬렉션의 값만 변경해도 JPA가 변경 사항을 알아 내서 실제 DB에 Query를 날린다.
      • (영속성 전이가 되는 것처럼)
    • 컬렉션은 Member 소속의 단순한 값이기 때문에 Member에 모든 생명 주기를 맡긴다.
  2. 컬렉션 값 타입 수정 예시2 - List<Address> 수정
     System.out.println("============ START ============");
     Member findMember = em.find(Member.class, member.getId());
     // old1 -> newCity1
     findMember.getAddressHistory().remove(new Address("old1", "street1", "10001")); // equals 로 비교 
     findMember.getAddressHistory().add(new Address("newCity1", "street1", "10001"));
    
    • equals(), hashCode() 에 대한 제대로 된 재정의가 필요하다.
    • AddressHistory 테이블에서 Member 에 소속된 Address 를 모두 지운다.
      • 테이블에 있는 데이터를 갈아 끼운다.
      • old2, newCity1 에 대한 Address 를 새로 INSERT 한다.
    • 아래 제약사항 참고!

값 타입 컬렉션의 제약사항

값 타입 컬렉션 대안

대안에 대한 예시 코드

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

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

    @Id @GeneratedValue
    private Long id;

    private Address address; // 값 타입 
}
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("homeCity", "street", "10000"));

member.getAddressHistory().add(new AddressEntity("old1", "street1", "10001"));
member.getAddressHistory().add(new AddressEntity("old2", "street2", "10002"));

em.persist(member);

em.flush();
em.clear();

정리


관련된 Post

Reference