JPA 기본 | 데이터 타입(3) - 값 타입 컬렉션

2024. 7. 14. 22:28· 인프런 김영한 강의 정리/자바 ORM 표준 JPA 프로그래밍 기본편
목차
  1. 1. 값 타입 컬렉션
  2. 2. 값 타입 컬렉션 특징
  3. 3. 값 타입 컬렉션 대신 일대다 관계 사용하기
  4. 4. 주의할 점
728x90

1. 값 타입 컬렉션

JPA에서 엔티티가 여러 개의 동일한 값 타입을 컬렉션으로 하나 이상 저장할 때 사용한다.
말이 좀 헷갈리니 바로 코드로 보자.

Member 엔티티와 값 타입 컬렉션 구조

✅값 타입 컬렉션 사용 예시

@Entity
@Getter
@Setter
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;

    @Embedded
    private Period workPeriod;

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

    public void setHomeAddress(Address homeAddress){
        this.homeAddress = homeAddress.copy();

    }
}
  • @ElementCollection을 사용한다.
  • 컬렉션을 저장하기 위해 별도의 테이블이 필요하다.  -> @CollectionTable로 테이블명 지정.

✅main 메서드(저장, 조회, 수정)

public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try{
            //멤버 엔티티 저장
            Member member = new Member();
            member.setUsername("memberA");
            member.setHomeAddress(new Address("homeCity","street","10000"));

            //FavoriteFood 저장
            member.getFavoriteFoods().add("치킨");
            member.getFavoriteFoods().add("피자");
            member.getFavoriteFoods().add("족발");

            //Address 저장
            member.getAddressesHistory().add(new Address("oldCity1","street","10000"));
            member.getAddressesHistory().add(new Address("oldCity2","street","10000"));

            em.persist(member);
            em.flush();
            em.clear();

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

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

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

            //수정 (List<Address>)
            findMember.getAddressesHistory().remove(new Address("oldCity1", "street", "10000"));
            findMember.getAddressesHistory().add(new Address("newCity1", "street", "10000"));

            //수정 (Set<String>)
            findMember.getFavoriteFoods().remove("치킨");
            findMember.getFavoriteFoods().add("햄버거");

            tx.commit();
        }catch (Exception e){
            tx.rollback();
            e.printStackTrace(); //예외 출력
        }finally {
            em.close();
        }
    }
  • 값 타입 컬렉션도 기본적으로 지연 로딩 전략을 사용한다. 
  • set으로 수정을 하는 것이 아니라 remove()로 삭제를 했다가 add()로 추가해줘야한다.

✅수정쿼리 비교(List<Address> VS Set<String>)
✔️List<Address> 수정 쿼리

Hibernate: 
    /* one-shot delete for hellojpa.Member.addressesHistory */
    delete from
        ADDRESS 
    where
        MEMBER_ID=?
Hibernate: 
    /* insert for
        hellojpa.Member.addressesHistory */
        insert into
        ADDRESS (MEMBER_ID, city, street, zipcode) 
    values
        (?, ?, ?, ?)
Hibernate: 
    /* insert for
        hellojpa.Member.addressesHistory */
        insert into
        ADDRESS (MEMBER_ID, city, street, zipcode) 
    values
        (?, ?, ?, ?)

findMember.getAddressesHistory().remove(new Address("oldCity1", "street", "10000"));
findMember.getAddressesHistory().add(new Address("newCity1", "street", "10000"));
위 코드를 보면,
remove()하는 값과 동일한걸 찾아서 해당하는 값만 삭제하고 add()하는 값만 삽입할 것 같은데, 실제 동작하는 쿼리는 다르다.
 
임베디드 타입 컬렉션에 변경 사항이 생기면, 주인 엔티티(여기서는 findMember)와 관련된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다. 
💡실무에서는 값 타입 컬렉션 대신에 일대다 관계를 사용한다. (영속성 전이, 고아객체 제거 옵션을 사용해서 값 타입 컬렉션처럼 사용)
 
✔️Set<String> 수정 코드

Hibernate: 
    /* delete for hellojpa.Member.favoriteFoods */
    delete from
        FAVORITE_FOOD 
    where
        MEMBER_ID=? 
        and FOOD_NAME=?
Hibernate: 
    /* insert for
        hellojpa.Member.favoriteFoods */
        insert into
        FAVORITE_FOOD (MEMBER_ID, FOOD_NAME) 
    values
        (?, ?)

- WHERE 조건절에 FOOD_NAME을 비교하는 조건이 있으니 이 값 타입은 원하는 값만 지우고 원하는 값만 삽입한다.
 

2. 값 타입 컬렉션 특징

  • 값 타입은 엔티티와 다르게 식별자 개념이 없다.
  • 값을 변경하면 추적이 어렵다.
  • 생명 주기를 엔티티에 의존한다.
  • 공유하지 않는 것이 안전하다.(복사해서 사용)
  • 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본키를 구성해야한다.

 

3. 값 타입 컬렉션 대신 일대다 관계 사용하기

List<Address> 수정 쿼리를 보면 주인 엔티티와 관련된 모든 데이터를 삭제한 후, 값 타입 컬렉션에 있는 모든 값을 다시 저장하는 것을 볼 수 있다.
이 문제를 해결하기 위해 실무에서는 일대다 관계로 풀어내기도 한다.
 
✅AddressEntity 엔티티 추가

@Entity
@Getter
@Setter
@Table(name="ADDRESS")
public class AddressEntity {

    @Id @GeneratedValue
    private Long id;

    private Address address;

    public AddressEntity() {
    }

    public AddressEntity(Address address) {this.address = address;}

    public AddressEntity(String city, String street, String zipcode) {
        this.address = new Address(city, street, zipcode);
    }
}
  • AddressEntity를 만들어 식별자로 테이블을 관리할 수 있다.

 
✅Member 엔티티 수정

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

	
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "MEMBER_ID")
    private List<AddressEntity> addressesHistory = new ArrayList<>();
  • 값 타입 컬렉션 대신 일대다 관계로 매핑했다.
  • 영속성 전이, 고아 객체 제거 옵션을 추가해서 값 타입 컬렉션처럼 사용할 수 있다.

4. 주의할 점

  • 값 타입은 정말 값 타입일때만 사용한다.
  • 식별자가 필요하고, 지속적으로 값을 추적하고 변경해야한다면 값타입이 아닌 엔티티다.
728x90

'인프런 김영한 강의 정리 > 자바 ORM 표준 JPA 프로그래밍 기본편' 카테고리의 다른 글

JPA 기본 | JPQL 기본 문법(1) 쿼리 API, 파라미터 바인딩  (0) 2024.07.15
JPA 기본 | 객체지향 쿼리 언어 알아보기(JPQL, QueryDSL 등)  (0) 2024.07.15
JPA 기본 | 데이터 타입(2) - 값 타입 공유와 불변객체  (0) 2024.07.14
JPA 기본 | 데이터 타입(1) - 기본 값과 임베디드 값 타입  (0) 2024.07.14
JPA 기본 | 영속성 전이(CASCADE)와 고아 객체  (0) 2024.07.13
  1. 1. 값 타입 컬렉션
  2. 2. 값 타입 컬렉션 특징
  3. 3. 값 타입 컬렉션 대신 일대다 관계 사용하기
  4. 4. 주의할 점
'인프런 김영한 강의 정리/자바 ORM 표준 JPA 프로그래밍 기본편' 카테고리의 다른 글
  • JPA 기본 | JPQL 기본 문법(1) 쿼리 API, 파라미터 바인딩
  • JPA 기본 | 객체지향 쿼리 언어 알아보기(JPQL, QueryDSL 등)
  • JPA 기본 | 데이터 타입(2) - 값 타입 공유와 불변객체
  • JPA 기본 | 데이터 타입(1) - 기본 값과 임베디드 값 타입
백엔드 개발자 - 젤리곰
백엔드 개발자 - 젤리곰
오늘도 배움이 있는 하루가 되길 바라는 개발자
백엔드 개발자 - 젤리곰
backend-gummyBear
백엔드 개발자 - 젤리곰
전체
오늘
어제
  • 분류 전체보기 (144)
    • 인프런 김영한 강의 정리 (60)
      • 스프링 핵심원리 기본편 (12)
      • 모든 개발자를 위한 HTTP 웹 기본 지식 (10)
      • 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (3)
      • 자바 ORM 표준 JPA 프로그래밍 기본편 (28)
      • 실전! Querydsl (6)
    • Spring (2)
    • 프로젝트일지 (6)
    • 프로그래밍 언어 (20)
      • Java (17)
      • JavaScript (3)
      • Python (0)
    • 데이터베이스 (4)
      • Oracle (2)
      • ORM (1)
      • SQL 튜닝 (1)
    • 형상관리 (1)
      • Git (0)
    • 알고리즘&자료구조 (34)
      • Algorithm (31)
      • Data Structure (1)
    • CS지식 (4)
    • Cloud (5)
    • 일기 (7)
      • 공부 일기 (3)
      • 독서 일기 (2)
      • 마음 일기 (2)

블로그 메뉴

  • 홈
  • 태그

공지사항

인기 글

태그

  • 다운캐스팅
  • LeetCode200번
  • jquery와javascript
  • 업캐스팅
  • 인터페이스
  • 프론트엔드개발자업무
  • 객체지향방법론
  • dfs알고리즘
  • 인프콘
  • 스프링컨텍스트
  • SublimeText단축키
  • #{}와${}의차이
  • 프론트엔드역사
  • ORM프레임워크
  • LeetCode17번
  • 클라이언트서버통신
  • 데이터베이스정규화
  • 객체지향의사실과오해
  • 힙자료구조
  • 커스텀annotation

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.2
백엔드 개발자 - 젤리곰
JPA 기본 | 데이터 타입(3) - 값 타입 컬렉션
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.