[개발] - Spring/JPA 공부

값 타입 컬렉션

완벽한 장면 2023. 8. 27. 19:50

미리 결론 스포, 값 타입 컬렉션은 언제 쓰는가?

=> 매우 단순한 상황일때만 쓰자!(라고 하기도 좀 그렇고, 쓰는 걸 고려하자 정도가 맞다.)

=> 대부분 다 엔티티로 만들어서 사용한다.

 

값 타입 컬렉션

  • 값 타입을 컬렉션에 담아서 쓰는 것

그림

값 타입 컬렉션

 

예제 

Member 클래스 변경

@Entity
@NoArgsConstructor
public class Member {

  @Id
  @GeneratedValue
  @Column(name = "MEMBER_ID")
  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") // 얘는 값이 String 하나고 내가 정의한 게 아니므로. 예외적 허용.
  private Set<String> favoriteFoods = new HashSet<>();

  @ElementCollection
  @CollectionTable(name = "ADDRESS",
                  joinColumns = @JoinColumn(name = "MEMBER_ID")) // 이러면 얘를 외래키로 잡게 됨.
  private List<Address> addressHistory = new ArrayList<>();

  public Long getId() {
    return id;
  }

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

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

}

 

 

실행하면

이러한 형태(유의깊게 봐야할 부분)

 


값 타입 컬렉션 사용

1. 값 타입 저장 예제

Member에 새로 만든 필드들에 getter, setter를 추가해주고

<전체> 

@Entity
@NoArgsConstructor
public class Member {

  @Id
  @GeneratedValue
  @Column(name = "MEMBER_ID")
  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") // 얘는 값이 String 하나고 내가 정의한 게 아니므로. 예외적 허용.
  private Set<String> favoriteFoods = new HashSet<>();

  @ElementCollection
  @CollectionTable(name = "ADDRESS",
                  joinColumns = @JoinColumn(name = "MEMBER_ID")) // 이러면 얘를 외래키로 잡게 됨.
  private List<Address> addressHistory = new ArrayList<>();

  public Long getId() {
    return id;
  }

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

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public Address getHomeAddress() {
    return homeAddress;
  }

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

  public Set<String> getFavoriteFoods() {
    return favoriteFoods;
  }

  public void setFavoriteFoods(Set<String> favoriteFoods) {
    this.favoriteFoods = favoriteFoods;
  }

  public List<Address> getAddressHistory() {
    return addressHistory;
  }

  public void setAddressHistory(List<Address> addressHistory) {
    this.addressHistory = addressHistory;
  }
}

 

JpaMain에도 저장을 시켜주는 코드를 작성한다.

<전체>

public class JpaMain {
  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("member1");
      member.setHomeAddress(new Address("homeCity", "street", "10000")); 
      //이렇게 하면 Member 테이블에 값들이 들어갈 거예요.

      // FAVORITEFOODS 컬렉션에 아래 세 가지가 들어간다.
      member.getFavoriteFoods().add("치킨");
      member.getFavoriteFoods().add("족발");
      member.getFavoriteFoods().add("피자");

      member.getAddressHistory().add(new Address("oldCity1", "street", "10000"));
      member.getAddressHistory().add(new Address("oldCity2", "street", "10000"));

      em.persist(member); // 저장 (하나만 함)


      tx.commit();
    } catch (Exception e) {
      tx.rollback();
      e.printStackTrace();
    } finally {
      em.close();
    }
    emf.close();
  }
}

 

실행 결과

18:58:30.056 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    /* insert inflearn.exjpa.jpaExample.Member
        */ insert 
        into
            Member
            (city, street, zipcode, USERNAME, MEMBER_ID) 
        values
            (?, ?, ?, ?, ?)


18:58:30.060 [main] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Inserting collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
18:58:30.060 [main] DEBUG org.hibernate.SQL - 
   
Hibernate: 
    /* insert collection
        row inflearn.exjpa.jpaExample.Member.addressHistory */ insert 
        into
            ADDRESS
            (MEMBER_ID, city, street, zipcode) 
        values
            (?, ?, ?, ?)

 

18:58:30.061 [main] DEBUG org.hibernate.SQL - 
   
Hibernate: 
    /* insert collection
        row inflearn.exjpa.jpaExample.Member.addressHistory */ insert 
        into
            ADDRESS
            (MEMBER_ID, city, street, zipcode) 
        values
            (?, ?, ?, ?)


18:58:30.063 [main] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Inserting collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
18:58:30.063 [main] DEBUG org.hibernate.SQL - 
    

 

Hibernate: 
    /* insert collection
        row inflearn.exjpa.jpaExample.Member.favoriteFoods */ insert 
        into
            FAVORITE_FOOD
            (MEMBER_ID, FOOD_NAME) 
        values
            (?, ?)


18:58:30.064 [main] DEBUG org.hibernate.SQL - 
    
Hibernate: 
    /* insert collection
        row inflearn.exjpa.jpaExample.Member.favoriteFoods */ insert 
        into
            FAVORITE_FOOD
            (MEMBER_ID, FOOD_NAME) 
        values
            (?, ?)

 

18:58:30.064 [main] DEBUG org.hibernate.SQL - 
    
Hibernate: 
    /* insert collection
        row inflearn.exjpa.jpaExample.Member.favoriteFoods */ insert 
        into
            FAVORITE_FOOD
            (MEMBER_ID, FOOD_NAME) 
        values
            (?, ?)



설명

1.

Hibernate: 
    /* insert inflearn.exjpa.jpaExample.Member
        */ insert 
        into
            Member
            (city, street, zipcode, USERNAME, MEMBER_ID) 
        values
            (?, ?, ?, ?, ?) 

 

여기서 이 3개는 

member.setHomeAddress(new Address("homeCity", "street", "10000"));  이거다.

 

 

2.

addressHistory를 두 번 insert 함.

Hibernate: 
    /* insert collection
        row inflearn.exjpa.jpaExample.Member.addressHistory */ insert 
        into
            ADDRESS
            (MEMBER_ID, city, street, zipcode) 
        values
            (?, ?, ?, ?)

 

18:58:30.061 [main] DEBUG org.hibernate.SQL - 
   
Hibernate: 
    /* insert collection
        row inflearn.exjpa.jpaExample.Member.addressHistory */ insert 
        into
            ADDRESS
            (MEMBER_ID, city, street, zipcode) 
        values
            (?, ?, ?, ?)

 

이거는 

      member.getAddressHistory().add(new Address("oldCity1", "street", "10000"));
      member.getAddressHistory().add(new Address("oldCity2", "street", "10000"));

이거 때문이다.

 

3.

favoriteFoods는 3번 insert 된다.

 

Hibernate: 
    /* insert collection
        row inflearn.exjpa.jpaExample.Member.favoriteFoods */ insert 
        into
            FAVORITE_FOOD
            (MEMBER_ID, FOOD_NAME) 
        values
            (?, ?)


18:58:30.064 [main] DEBUG org.hibernate.SQL - 
    
Hibernate: 
    /* insert collection
        row inflearn.exjpa.jpaExample.Member.favoriteFoods */ insert 
        into
            FAVORITE_FOOD
            (MEMBER_ID, FOOD_NAME) 
        values
            (?, ?)

 

18:58:30.064 [main] DEBUG org.hibernate.SQL - 
    
Hibernate: 
    /* insert collection
        row inflearn.exjpa.jpaExample.Member.favoriteFoods */ insert 
        into
            FAVORITE_FOOD
            (MEMBER_ID, FOOD_NAME) 
        values
            (?, ?)

 

 

H2 결과 확인해보면

문제 없이 잘 들어가 있다.


흥미로운 부분은,

값 타입 컬렉션을 지금 따로 persist 하지 않았음.

그런데 member만 하니까 값 타입 컬렉션들이 자동으로 들어갔다.

 

homeAddress는 소속된 임베디드 값 타입이니까 당연히 들어간거고

컬렉션도 다른 테이블임에도 불구하고 라이프사이클이 같이 돌아간 것.

-> 값 타입 컬렉션 때문에 그렇다.

값 타입 컬렉션은 본인 스스로의 라이프사이클이 없다. 모든 생명주기가 Member에 소속되어 있는 것.

값 타입 컬렉션도 값 타입이므로 전부 Member에 의존.

 

그래서 Member에서 값을 바꾸면 자동으로 변경됨.

 


2. 값 타입 조회 예제

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

JPAMain 아래에다가

public class JpaMain {
  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("member1");
      member.setHomeAddress(new Address("homeCity", "street", "10000")); 
      //Member 테이블에 값들이 들어갈 거예요.

      // 이 FAVORITEFOODS 컬렉션에 세 가지가 들어간다.
      member.getFavoriteFoods().add("치킨");
      member.getFavoriteFoods().add("족발");
      member.getFavoriteFoods().add("피자");

      member.getAddressHistory().add(new Address("oldCity1", "street", "10000"));
      member.getAddressHistory().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());

      tx.commit();
    } catch (Exception e) {
      tx.rollback();
      e.printStackTrace();
    } finally {
      em.close();
    }
    emf.close();
  }
}

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

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

이거 추가하고 일단 실행시켜보면

 

============START============
19:46:41.075 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    select
        member0_.MEMBER_ID as member_i1_6_0_,
        member0_.city as city2_6_0_,
        member0_.street as street3_6_0_,
        member0_.zipcode as zipcode4_6_0_,
        member0_.USERNAME as username5_6_0_ 
    from
        Member member0_ 
    where
        member0_.MEMBER_ID=?

 

이렇게 나온다.

지금 내가 Member만 조회했으니 member만 가지고 오는 상황.

이를 통해 값 타입 컬렉션도 지연 로딩이다는 것을 확인 가능.

 

그런데 또 재밌는 것은 

@Embedded

private Address homeAddress;

는 Member에 소속된 값 타입이기 때문에 같이 조회가 된다.

 

        member0_.city as city2_6_0_,
        member0_.street as street3_6_0_,
        member0_.zipcode as zipcode4_6_0_,

얘는 굳이 지연로딩 할 필요가 없지.

 

그래서 굳이 찍어줘야 쿼리가 나갈 것이다.

추가한 부분 아래

 

List<Address> addresssHistory = findMember.getAddressHistory();
for (Address address : addresssHistory) { // iter 라고만 쓰면 알아서 향상된 for문 나옴.
	System.out.println("address = " + address.getCity());
}

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

 

그냥 전체적 코드 한 번 더 쓰면

public class JpaMain {
  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("member1");
      member.setHomeAddress(new Address("homeCity", "street", "10000")); //Member 테이블에 값들이 들어갈 거예요.

      // 이 FAVORITEFOODS 컬렉션에 세 가지가 들어간다.
      member.getFavoriteFoods().add("치킨");
      member.getFavoriteFoods().add("족발");
      member.getFavoriteFoods().add("피자");

      member.getAddressHistory().add(new Address("oldCity1", "street", "10000"));
      member.getAddressHistory().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> addresssHistory = findMember.getAddressHistory();
      for (Address address : addresssHistory) { // iter 라고만 쓰면 알아서 향상된 for문 나옴.
        System.out.println("address = " + address.getCity());
      }

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

      tx.commit();
    } catch (Exception e) {
      tx.rollback();
      e.printStackTrace();
    } finally {
      em.close();
    }
    emf.close();
  }
}

 

실행해보면

============START============
19:57:34.860 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    select
        member0_.MEMBER_ID as member_i1_6_0_,
        member0_.city as city2_6_0_,
        member0_.street as street3_6_0_,
        member0_.zipcode as zipcode4_6_0_,
        member0_.USERNAME as username5_6_0_ 
    from
        Member member0_ 
    where
        member0_.MEMBER_ID=?

19:57:34.868 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    select
        addresshis0_.MEMBER_ID as member_i1_0_0_,
        addresshis0_.city as city2_0_0_,
        addresshis0_.street as street3_0_0_,
        addresshis0_.zipcode as zipcode4_0_0_ 
    from
        ADDRESS addresshis0_ 
    where
        addresshis0_.MEMBER_ID=?
        
19:57:34.869 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl - Preparing collection initializer : [inflearn.exjpa.jpaExample.Member.addressHistory#1]
19:57:34.871 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
19:57:34.872 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
19:57:34.872 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections were found in result set for role: inflearn.exjpa.jpaExample.Member.addressHistory
19:57:34.872 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - Collection fully initialized: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
19:57:34.872 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections initialized for role: inflearn.exjpa.jpaExample.Member.addressHistory
19:57:34.872 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Done loading collection

address = oldCity1
address = oldCity2


19:57:34.872 [main] DEBUG org.hibernate.SQL - 
    
Hibernate: 
    select
        favoritefo0_.MEMBER_ID as member_i1_4_0_,
        favoritefo0_.FOOD_NAME as food_nam2_4_0_ 
    from
        FAVORITE_FOOD favoritefo0_ 
    where
        favoritefo0_.MEMBER_ID=?
        
19:57:34.873 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl - Preparing collection initializer : [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
19:57:34.873 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
19:57:34.873 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
19:57:34.873 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
19:57:34.873 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections were found in result set for role: inflearn.exjpa.jpaExample.Member.favoriteFoods
19:57:34.873 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - Collection fully initialized: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
19:57:34.873 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections initialized for role: inflearn.exjpa.jpaExample.Member.favoriteFoods
19:57:34.873 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Done loading collection

favoriteFood = 족발
favoriteFood = 치킨
favoriteFood = 피자

후략

종료 코드 0(으)로 완료된 프로세스

지연 로딩임을 확인할 수 있음.

 


3. 값 타입 수정 예제

값을 좀 바꿔서

물론 먼저, 이건 컬렉션 내용은 아니다.

public class JpaMain {
  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("member1");
      member.setHomeAddress(new Address("homeCity", "street", "10000")); //Member 테이블에 값들이 들어갈 거예요.

      // 이 FAVORITEFOODS 컬렉션에 세 가지가 들어간다.
      member.getFavoriteFoods().add("치킨");
      member.getFavoriteFoods().add("족발");
      member.getFavoriteFoods().add("피자");

      member.getAddressHistory().add(new Address("oldCity1", "street", "10000"));
      member.getAddressHistory().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());

      // iter지우고 수정 예제
      //이렇게 하면 일단 안 된다. sideEffect 발생
//      findMember.getHomeAddress().setCity("newCity");

      // 답은, 아예 새로 넣기다.(통으로 값 타입을 갈아끼운다. 완전한 교체
      // 도시명만 바꾸고 나머지는 그대로 하고싶다고 가정.
      Address origin = findMember.getHomeAddress();
      findMember.setHomeAddress(new Address("newCity", origin.getStreet(), origin.getZipcode()));

      tx.commit();
    } catch (Exception e) {
      tx.rollback();
      e.printStackTrace();
    } finally {
      em.close();
    }
    emf.close();
  }
}

 

실행하면

============START============
20:42:06.226 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    select
        member0_.MEMBER_ID as member_i1_6_0_,
        member0_.city as city2_6_0_,
        member0_.street as street3_6_0_,
        member0_.zipcode as zipcode4_6_0_,
        member0_.USERNAME as username5_6_0_ 
    from
        Member member0_ 
    where
        member0_.MEMBER_ID=?
중략

20:42:06.240 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    /* update
        inflearn.exjpa.jpaExample.Member */ update
            Member 
        set
            city=?,
            street=?,
            zipcode=?,
            USERNAME=? 
        where
            MEMBER_ID=?

조회해 온 다음에 업데이트 쿼리 나감을 확인할 수 있음.

 

이제 컬렉션을 수정해보면

// 이제 값 타입 컬렉션 업데이트
// 컬렉션 내의 치킨 => 힌식으로 변경
findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");

아예 지우고, 새로운 것을 넣어라.

 

실행 결과를 보면

21:03:25.894 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    /* delete collection row inflearn.exjpa.jpaExample.Member.favoriteFoods */ delete 
        from
            FAVORITE_FOOD 
        where
            MEMBER_ID=? 
            and FOOD_NAME=?
21:03:25.894 [main] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Done deleting collection rows: 1 deleted

// 지우고

 

// 집어넣는다.
21:03:25.894 [main] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Inserting rows of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
21:03:25.895 [main] DEBUG org.hibernate.SQL - 
    
Hibernate: 
    /* insert collection
        row inflearn.exjpa.jpaExample.Member.favoriteFoods */ insert 
        into
            FAVORITE_FOOD
            (MEMBER_ID, FOOD_NAME) 
        values
            (?, ?)

 

컬렉션의 값만 변경해도 실제 데잍베이스 쿼리가 날아가면서 뭐가 변경되었는지 알고 JPA가 알아서 바꿔준다.

마치 영속성 전이 처럼.

 

 

이제 주소 바꾸기

 

 

 

실행하면

============START============
중략 군데군데 중략

21:13:35.032 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl - Preparing collection initializer : [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
21:13:35.033 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
21:13:35.033 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
21:13:35.033 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
21:13:35.033 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections were found in result set for role: inflearn.exjpa.jpaExample.Member.favoriteFoods
21:13:35.034 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - Collection fully initialized: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
21:13:35.034 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections initialized for role: inflearn.exjpa.jpaExample.Member.favoriteFoods
21:13:35.034 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Done loading collection
21:13:35.034 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Loading collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
21:13:35.034 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    select
        addresshis0_.MEMBER_ID as member_i1_0_0_,
        addresshis0_.city as city2_0_0_,
        addresshis0_.street as street3_0_0_,
        addresshis0_.zipcode as zipcode4_0_0_ 
    from
        ADDRESS addresshis0_ 
    where
        addresshis0_.MEMBER_ID=?
21:13:35.034 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl - Preparing collection initializer : [inflearn.exjpa.jpaExample.Member.addressHistory#1]
21:13:35.035 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
21:13:35.035 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
21:13:35.035 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections were found in result set for role: inflearn.exjpa.jpaExample.Member.addressHistory
21:13:35.035 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - Collection fully initialized: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
21:13:35.035 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections initialized for role: inflearn.exjpa.jpaExample.Member.addressHistory
21:13:35.035 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Done loading collection
21:13:35.035 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - committing
21:13:35.035 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Processing flush-time cascades
21:13:35.035 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Dirty checking collections
21:13:35.035 [main] DEBUG org.hibernate.engine.spi.CollectionEntry - Collection dirty: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
21:13:35.035 [main] DEBUG org.hibernate.engine.spi.CollectionEntry - Collection dirty: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
21:13:35.035 [main] DEBUG org.hibernate.engine.internal.Collections - Collection found: [inflearn.exjpa.jpaExample.Member.addressHistory#1], was: [inflearn.exjpa.jpaExample.Member.addressHistory#1] (initialized)
21:13:35.035 [main] DEBUG org.hibernate.engine.internal.Collections - Collection found: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1], was: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1] (initialized)
21:13:35.035 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 0 insertions, 1 updates, 0 deletions to 1 objects
21:13:35.035 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 0 (re)creations, 2 updates, 0 removals to 2 collections
21:13:35.035 [main] DEBUG org.hibernate.internal.util.EntityPrinter - Listing entities:
21:13:35.036 [main] DEBUG org.hibernate.internal.util.EntityPrinter - inflearn.exjpa.jpaExample.Member{id=1, addressHistory=[component[city,street,zipcode]{zipcode=10000, city=oldCity2, street=street}, component[city,street,zipcode]{zipcode=10000, city=newCity1, street=street}], favoriteFoods=[족발, 한식, 피자], homeAddress=component[city,street,zipcode]{zipcode=10000, city=newCity, street=street}, username=member1}
21:13:35.036 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    /* update
        inflearn.exjpa.jpaExample.Member */ update
            Member 
        set
            city=?,
            street=?,
            zipcode=?,
            USERNAME=? 
        where
            MEMBER_ID=?
21:13:35.039 [main] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Deleting collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
21:13:35.039 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    /* delete collection inflearn.exjpa.jpaExample.Member.addressHistory */ delete 
        from
            ADDRESS 
        where
            MEMBER_ID=?
21:13:35.040 [main] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Done deleting collection
21:13:35.040 [main] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Inserting collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
21:13:35.040 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    /* insert collection
        row inflearn.exjpa.jpaExample.Member.addressHistory */ insert 
        into
            ADDRESS
            (MEMBER_ID, city, street, zipcode) 
        values
            (?, ?, ?, ?)
21:13:35.041 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    /* insert collection
        row inflearn.exjpa.jpaExample.Member.addressHistory */ insert 
        into
            ADDRESS
            (MEMBER_ID, city, street, zipcode) 
        values
            (?, ?, ?, ?)

 

Member에 소속된 데이터 중에 ADRESS를 다 지움.

그리고 insert 쿼리가 두 번 날아가는데, remove로 oldCity1을 날렸어도, oldCity2가 남아있으니까.

두 개를 다 완전히 갈아끼워 버린 것. oldCity1과 newCity1을 다시 각각 인서트하므로 쿼리 두 번 날아감.

 


값 타입 컬렉션의 제약사항

@OrderColumn 어노테이션을 쓰기도 하는데, 허점이 너무 많다.


 

********* 값 타입 컬렉션의  대안 (매우 중요)

 

 

예제 - AddressEntity 사용

AddressEntity 생성

@Entity
@Table(name = "ADDRESS")
@NoArgsConstructor
@AllArgsConstructor
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 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 변경(수정한 부분 그냥 주석처리)

@Entity
@NoArgsConstructor
public class Member {

  @Id
  @GeneratedValue
  @Column(name = "MEMBER_ID")
  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") // 얘는 값이 String 하나고 내가 정의한 게 아니므로. 예외적 허용.
  private Set<String> favoriteFoods = new HashSet<>();

//  @ElementCollection
//  @CollectionTable(name = "ADDRESS",
//                  joinColumns = @JoinColumn(name = "MEMBER_ID")) // 이러면 얘를 외래키로 잡게 됨.
//  private List<Address> addressHistory = new ArrayList<>();


// 여기
  //위에 것을 아래처럼 만들 수 있음.
  // 일대다 단방향 매핑으로 만들자.
  @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
  @JoinColumn(name = "MEMBER_ID")
  private List<AddressEntity> addressHistory = new ArrayList<>();

  public Long getId() {
    return id;
  }

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

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public Address getHomeAddress() {
    return homeAddress;
  }

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

  public Set<String> getFavoriteFoods() {
    return favoriteFoods;
  }

  public void setFavoriteFoods(Set<String> favoriteFoods) {
    this.favoriteFoods = favoriteFoods;
  }

//  public List<Address> getAddressHistory() {
//    return addressHistory;
//  }
//
//  public void setAddressHistory(List<Address> addressHistory) {
//    this.addressHistory = addressHistory;
//  }

  public List<AddressEntity> getAddressHistory() {
    return addressHistory;
  }

  public void setAddressHistory(List<AddressEntity> addressHistory) {
    this.addressHistory = addressHistory;
  }
}

 

 

JPAMain도 코드 변경

public class JpaMain {
  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("member1");
      member.setHomeAddress(new Address("homeCity", "street", "10000")); 
      //Member 테이블에 값들이 들어갈 거예요.

      // 이 FAVORITEFOODS 컬렉션에 세 가지가 들어간다.
      member.getFavoriteFoods().add("치킨");
      member.getFavoriteFoods().add("족발");
      member.getFavoriteFoods().add("피자");

//      member.getAddressHistory().add(new Address("oldCity1", "street", "10000"));
//      member.getAddressHistory().add(new Address("oldCity2", "street", "10000"));
// 이렇게 바꾼다. 위를 아래로
      member.getAddressHistory().add(new AddressEntity("oldCity1", "street", "10000"));

      em.persist(member); // 저장

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

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


      tx.commit();
    } catch (Exception e) {
      tx.rollback();
      e.printStackTrace();
    } finally {
      em.close();
    }
    emf.close();
  }
}

 

 

실행 결과 보면

필요한 것만 따왔음.

일대다로 해서 저장 잘 됨.

22:07:42.605 [main] DEBUG org.hibernate.SQL - 
    

Hibernate: 
    
    create table ADDRESS (
       id bigint not null,
        city varchar(255),
        street varchar(255),
        zipcode varchar(255),
        MEMBER_ID bigint,
        primary key (id)
    )

22:07:42.608 [main] DEBUG org.hibernate.SQL - 
    

Hibernate: 
    
    create table Member (
       MEMBER_ID bigint not null,
        city varchar(255),
        street varchar(255),
        zipcode varchar(255),
        USERNAME varchar(255),
        primary key (MEMBER_ID)
    )

Hibernate: 
    
    alter table ADDRESS 
       add constraint FKsuulxb5rmrxvb83yr43ox86wn 
       foreign key (MEMBER_ID) 
       references Member
22:07:42.616 [main] DEBUG org.hibernate.SQL - 
    

22:07:42.639 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator - No JtaPlatform was specified, checking resolver
22:07:42.639 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformResolverInitiator - No JtaPlatformResolver was specified, using default [org.hibernate.engine.transaction.jta.platform.internal.StandardJtaPlatformResolver]
22:07:42.642 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.StandardJtaPlatformResolver - Could not resolve JtaPlatform, using default [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
22:07:42.642 [main] INFO org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
22:07:42.645 [main] DEBUG org.hibernate.service.internal.SessionFactoryServiceRegistryImpl - EventListenerRegistry access via ServiceRegistry is deprecated.  Use `sessionFactory.getEventEngine().getListenerRegistry()` instead
22:07:42.646 [main] DEBUG org.hibernate.hql.internal.QueryTranslatorFactoryInitiator - QueryTranslatorFactory: org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory@45cd7bc5
22:07:42.648 [main] DEBUG org.hibernate.query.spi.NamedQueryRepository - Checking 0 named HQL queries
22:07:42.649 [main] DEBUG org.hibernate.query.spi.NamedQueryRepository - Checking 0 named SQL queries
22:07:42.649 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Initializing SessionFactoryRegistry : org.hibernate.internal.SessionFactoryRegistry@2e09c51
22:07:42.651 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Registering SessionFactory: 6dd942d0-cb20-4769-9f1d-f5d00c8de59d (<unnamed>)
22:07:42.651 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Not binding SessionFactory to JNDI, no JNDI name configured
22:07:42.691 [main] DEBUG org.hibernate.stat.internal.StatisticsInitiator - Statistics initialized [enabled=false]
22:07:42.696 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
22:07:42.696 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - begin
22:07:42.700 [main] DEBUG org.hibernate.SQL - 
    call next value for hibernate_sequence
Hibernate: 
    call next value for hibernate_sequence
22:07:42.704 [main] DEBUG org.hibernate.id.enhanced.SequenceStructure - Sequence value obtained: 1
22:07:42.704 [main] DEBUG org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl - HHH000387: ResultSet's statement was not registered
22:07:42.706 [main] DEBUG org.hibernate.event.internal.AbstractSaveEventListener - Generated identifier: 1, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
22:07:42.725 [main] DEBUG org.hibernate.SQL - 
    call next value for hibernate_sequence
Hibernate: 
    call next value for hibernate_sequence
22:07:42.726 [main] DEBUG org.hibernate.id.enhanced.SequenceStructure - Sequence value obtained: 2
22:07:42.726 [main] DEBUG org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl - HHH000387: ResultSet's statement was not registered
22:07:42.726 [main] DEBUG org.hibernate.event.internal.AbstractSaveEventListener - Generated identifier: 2, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
22:07:42.727 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Processing flush-time cascades
22:07:42.728 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Dirty checking collections
22:07:42.731 [main] DEBUG org.hibernate.engine.internal.Collections - Collection found: [inflearn.exjpa.jpaExample.Member.addressHistory#1], was: [<unreferenced>] (initialized)
22:07:42.732 [main] DEBUG org.hibernate.engine.internal.Collections - Collection found: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1], was: [<unreferenced>] (initialized)
22:07:42.733 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 2 insertions, 0 updates, 0 deletions to 2 objects
22:07:42.733 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 2 (re)creations, 0 updates, 0 removals to 2 collections
22:07:42.734 [main] DEBUG org.hibernate.internal.util.EntityPrinter - Listing entities:
22:07:42.734 [main] DEBUG org.hibernate.internal.util.EntityPrinter - inflearn.exjpa.jpaExample.Member{id=1, addressHistory=[inflearn.exjpa.jpaExample.AddressEntity#2], favoriteFoods=[족발, 치킨, 피자], homeAddress=component[city,street,zipcode]{zipcode=10000, city=homeCity, street=street}, username=member1}
22:07:42.734 [main] DEBUG org.hibernate.internal.util.EntityPrinter - inflearn.exjpa.jpaExample.AddressEntity{address=component[city,street,zipcode]{zipcode=10000, city=oldCity1, street=street}, id=2}
22:07:42.737 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    /* insert inflearn.exjpa.jpaExample.Member
        */ insert 
        into
            Member
            (city, street, zipcode, USERNAME, MEMBER_ID) 
        values
            (?, ?, ?, ?, ?)
22:07:42.741 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    /* insert inflearn.exjpa.jpaExample.AddressEntity
        */ insert 
        into
            ADDRESS
            (city, street, zipcode, id) 
        values
            (?, ?, ?, ?)
22:07:42.744 [main] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Inserting collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
22:07:42.744 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    /* create one-to-many row inflearn.exjpa.jpaExample.Member.addressHistory */ update
        ADDRESS 
    set
        MEMBER_ID=? 
    where
        id=?

============START============
22:07:42.758 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    select
        member0_.MEMBER_ID as member_i1_6_0_,
        member0_.city as city2_6_0_,
        member0_.street as street3_6_0_,
        member0_.zipcode as zipcode4_6_0_,
        member0_.USERNAME as username5_6_0_ 
    from
        Member member0_ 
    where
        member0_.MEMBER_ID=?
22:07:42.762 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl - On call to EntityIdentifierReaderImpl#resolve, EntityKey was already known; should only happen on root returns with an optional identifier specified
22:07:42.766 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Resolving attributes for [inflearn.exjpa.jpaExample.Member#1]
22:07:42.766 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `addressHistory` : value = NOT NULL COLLECTION
22:07:42.766 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`addressHistory`)  - enhanced for lazy-loading? - false
22:07:42.767 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `favoriteFoods` : value = NOT NULL COLLECTION
22:07:42.767 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`favoriteFoods`)  - enhanced for lazy-loading? - false
22:07:42.767 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `homeAddress` : value = [Ljava.lang.Object;@65fe2691
22:07:42.767 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`homeAddress`)  - enhanced for lazy-loading? - false
22:07:42.767 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `username` : value = member1
22:07:42.767 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`username`)  - enhanced for lazy-loading? - false
22:07:42.768 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done materializing entity [inflearn.exjpa.jpaExample.Member#1]
22:07:42.769 [main] DEBUG org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader - Done entity load : inflearn.exjpa.jpaExample.Member#1
22:07:42.770 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Loading collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
22:07:42.770 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    select
        favoritefo0_.MEMBER_ID as member_i1_4_0_,
        favoritefo0_.FOOD_NAME as food_nam2_4_0_ 
    from
        FAVORITE_FOOD favoritefo0_ 
    where
        favoritefo0_.MEMBER_ID=?
22:07:42.771 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl - Preparing collection initializer : [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
22:07:42.773 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
22:07:42.773 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
22:07:42.773 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
22:07:42.773 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections were found in result set for role: inflearn.exjpa.jpaExample.Member.favoriteFoods
22:07:42.773 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - Collection fully initialized: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
22:07:42.773 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections initialized for role: inflearn.exjpa.jpaExample.Member.favoriteFoods
22:07:42.773 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Done loading collection
22:07:42.773 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - committing
22:07:42.773 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Processing flush-time cascades
22:07:42.773 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Dirty checking collections
22:07:42.773 [main] DEBUG org.hibernate.engine.spi.CollectionEntry - Collection dirty: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
22:07:42.774 [main] DEBUG org.hibernate.engine.internal.Collections - Collection found: [inflearn.exjpa.jpaExample.Member.addressHistory#1], was: [inflearn.exjpa.jpaExample.Member.addressHistory#1] (uninitialized)
22:07:42.774 [main] DEBUG org.hibernate.engine.internal.Collections - Collection found: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1], was: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1] (initialized)
22:07:42.774 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 0 insertions, 1 updates, 0 deletions to 1 objects
22:07:42.774 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 0 (re)creations, 1 updates, 0 removals to 2 collections
22:07:42.774 [main] DEBUG org.hibernate.internal.util.EntityPrinter - Listing entities:
22:07:42.774 [main] DEBUG org.hibernate.internal.util.EntityPrinter - inflearn.exjpa.jpaExample.Member{id=1, addressHistory=<uninitialized>, favoriteFoods=[족발, 한식, 피자], homeAddress=component[city,street,zipcode]{zipcode=10000, city=newCity, street=street}, username=member1}
22:07:42.774 [main] DEBUG org.hibernate.SQL - 

Hibernate: 
    /* update
        inflearn.exjpa.jpaExample.Member */ update
            Member 
        set
            city=?,
            street=?,
            zipcode=?,
            USERNAME=? 
        where
            MEMBER_ID=?

 

그런데 일대다 다방향 매핑이라서 업데이트 쿼리 나가는 건 어쩔 수가 없다.

다른 테이블에 외래키가 있기 때문에.

 

그래서 데이터베이스 가서 확인하고 오면

!!!

여기 지금 ID 개념이 생겼다.

즉, 값 타입이 아니라 엔티티라는 것.

 

이렇게 되면 값을 가져와서 수정하는 것을 맘대로 할 수 있게 된다.

값 타입을 엔티티로 승급시킴.

 

 

 


정리

 

 

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

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

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