티스토리 뷰

인프런 - 김영한 JPA 강의 중

 

값 타입 컬렉션

- 값 타입을 하나 이상 저장할 때 사용

- @ElementCollection, @CollectionTable 사용

- 데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.

- 컬렉션을 저장하기 위한 별도의 테이블이 필요함

 

package jpabook.jpashop.domain;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Entity
public class Member extends BaseEntity{

    @Id @GeneratedValue
    private Long id;

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

    //주소
    @Embedded
    private Address address;

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

 

다음과 같이 collection에 어노테이션을 적용하면 

다음과 같이 db가 생성 된다.

 

값 타입 컬렉션 사용

- 값 타입 저장 예제

- 값 타입 조회 예제

ㄴ 값 타입 컬렉션도 지연 로딩 전략 사용

- 값 타입 수정 예제

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

            Member member = new Member();
            member.setUsername("member1");
            member.setAddress(new Address("city1","street","zipcode"));

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

            member.getAddressHistory().add(new Address("ccc","Ddd","Fdsfs"));
            member.getAddressHistory().add(new Address("ccc","Ddd","Fdsfs"));

            em.persist(member);

 

이 상태에서 조회를 해본다면

            Member member1 = em.find(Member.class, member.getId());
    select
        member0_.id as id1_2_0_,
        member0_.createBy as createBy2_2_0_,
        member0_.createDate as createDa3_2_0_,
        member0_.lastModifiedBy as lastModi4_2_0_,
        member0_.lastModifiedDate as lastModi5_2_0_,
        member0_.city as city6_2_0_,
        member0_.street as street7_2_0_,
        member0_.zipcode as zipcode8_2_0_,
        member0_.USERNAME as USERNAME9_2_0_ 
    from
        Member member0_ 
    where
        member0_.id=?

위와 같은 로그가 뜬다. 값을 넣었던 FavoriteFood와 AddressHistory가 함께 잡히지 않는다. 그것은 값 타입 컬렉션은 지연로딩이라는 뜻이 된다.

            Member member = new Member();
            member.setUsername("member1");
            member.setAddress(new Address("city1","street","zipcode"));

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

            member.getAddressHistory().add(new Address("ccc","Ddd","Fdsfs"));
            member.getAddressHistory().add(new Address("ccc","Ddd","Fdsfs"));

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

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

            //수정
            // findMember.getAddress().setCity("newCiry"); 값타입은 절대로 이렇게 바꾸면 안된다. 사고난다.
            // 통으로 갈아끼우기
            findMember.setAddress(new Address("newCiry","ddd","safdasd"));
            //치킨 -> 한식
            findMember.getFavoriteFoods().remove("치킨");
            findMember.getFavoriteFoods().add("한식");

            // 주소 변경 // remove는 equals를 사용해서 변경한다.
            findMember.getAddressHistory().remove(new Address("ccc","Ddd","Fdsfs"));
            findMember.getAddressHistory().add(new Address("cc124","Ddddas","Fds124fs"));

 

수정을 보자 값 타입 컬렉션에서 마지막 주소변경의 로그부분을 보면

 

Hibernate: 
    /* delete collection jpabook.jpashop.domain.Member.addressHistory */ delete 
        from
            ADDRESS 
        where
            MEMBER_ID=?
Hibernate: 
    /* insert collection
        row jpabook.jpashop.domain.Member.addressHistory */ insert 
        into
            ADDRESS
            (MEMBER_ID, city, street, zipcode) 
        values
            (?, ?, ?, ?)
Hibernate: 
    /* insert collection
        row jpabook.jpashop.domain.Member.addressHistory */ insert 
        into
            ADDRESS
            (MEMBER_ID, city, street, zipcode) 
        values
            (?, ?, ?, ?)

 

테이블의 데이터를 통으로 날린 뒤 삭제 지정이 안된 데이터가 등록되게 된다. (삭제 된 것은 다시 저장되지 않음)

 

값타입 컬렉션의 제약 사항

- 값 타입은 엔티티와 다르게 식별자 개념이 없다.

- 값은 변경하면 추적이 어렵다.

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

- 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본키를 구성해야 함 : null입력 x, 중복 저장 x

 

 

값 타입 컬렉션 대안

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

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

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

- EX) AddressEntity

 

package jpabook.jpashop.domain;

import org.hibernate.annotations.SelectBeforeUpdate;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

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

    @Id @GeneratedValue
    private Long id;

    private Address address;

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

    public AddressEntity() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}
            Member member = new Member();
            member.setUsername("member1");
            member.setAddress(new Address("city1","street","zipcode"));

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

            member.getAddressHistory().add(new AddressEntity("ccc","Ddd","Fdsfs"));
            member.getAddressHistory().add(new AddressEntity("ccc","Ddd","Fdsfs"));

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

 

엔티티로 인해 테이블은 생기지만 좀 더 효율적이게 된다.

 

Set(컬렉션타입)을 사용하는 값타입 컬렉션은 간단한 셀렉트박스 같은 값이 바껴도 상관 없는 애만 사용한다.

 

정리

- 엔티티 타입의 특징

ㄴ 식별자 o

ㄴ 생명주기 관리

ㄴ 공유

- 값 타입의 특징

ㄴ 식별자 x

ㄴ 생명 주기를 엔티티에 의존

ㄴ 공유하지 않는 것이 안전(복사해서 사용)

ㄴ 불변 객체로 만드는 것이 안전

 

값 타입은 정말 값 타입이라 판단될 때만 사용

 

엔티티와 깞 타입을 혼동해서 엔티티를 값 타입으로 만들면 안됨

 

식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면 그것은 값 타입이 아닌 엔티티

 

 

'dev_공부일지 > JPA 기본' 카테고리의 다른 글

JPQL - 기본문법과 쿼리 API  (0) 2024.02.08
객체지향 쿼리 언어 1  (1) 2024.02.08
값 타입의 비교  (0) 2024.02.06
값 타입과 불변 객체  (0) 2024.02.06
임베디드 타입  (1) 2024.02.06
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함