Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
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
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

값 타입 본문

인프런/[인프런] 자바ORM 표준 JPA 프로그래밍

값 타입

Seung__ 2021. 8. 13. 23:35

1. 값 타입 컬렉션이란??


  • 값 타입을 컬렉션에 담아 사용하는 것.
  • 관계형 DB는 기본적으로 내부에 컬렉션을 담을 방법이 없음. 값만 넣을 수 있음.
  • 그래서 컬렉션에 해당하는 것을 별도 테이블로 만들어야함. 즉 일대다 형태로 구성됨.
  • 단 이때 값타입의 테이블은 각 속성들을 모아서 PK로 사용해야함.
  • 만약 식별자를 만들어서 PK로 쓰면 엔티티가 됨 ㅋㅋㅋㅋ

 

 

 

2. 값 타입 컬렉션


  • 값 타입을 하나 이상 저상할 때 사용
  • @ElementCollection, @CollectionTable 사용
    • @ElementCollection을 통해 값 타입 컬렉션으로 지정
    • @ColellectionTable을 사용해 테이블 생성
    • 이 때 일종의 일대다 관계라 볼 수 있고, FAVORITE_FOOD와 ADDRESS가 "다"측이고,
      따라서 "일"의 PK를 가지고 있어야 한다. --> @JoinColumn
    •     @ElementCollection
          @CollectionTable(name = "FAVORITE_FOOD",
                  joinColumns = @JoinColumn(name = "MEMBER_ID")) //Collection table에 FK 넣어줌.FK로 잡게됨.
          @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<>();
  • 데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.
  • 컬렉션을 저장하기 위한 별도의 테이블이 필요함.
    • DB에는 컬렉션을 한 테이블에 담을 수 없다. 일대다 구조로 생성해야 한다.

 

 

 

3. table 생성 결과


  • 실행 결과
    • Member table에 값 타입 들어감.
      Hibernate: 
          
          create table Member (
             MEMBER_ID bigint not null,
              city varchar(255),
              street varchar(255),
              zipcode varchar(255),
              endDate timestamp,
              startDate timestamp,
              USERNAME varchar(255),
              TEAM_ID bigint,
              primary key (MEMBER_ID)
          )​
    • FAVORITE_TABLE, ADDRESS TABLE 생성
      Hibernate: 
          
          create table FAVORITE_FOOD (
             MEMBER_ID bigint not null,
              FOOD_NAME varchar(255)
          )​
          
      Hibernate: 
          
          create table ADDRESS (
             MEMBER_ID bigint not null,
              city varchar(255),
              street varchar(255),
              zipcode varchar(255)
          )

4. 값 타입 컬렉션 저장 예제


Member member = new Member();
member.setUserName("member1");
//address 값타입
member.setHomeAddress(new Address("homeCity", "street", "sdd2d"));

//값타입 컬렉션
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피쟈");

member.getAddressHistory().add(new Address("old1", "street", "65555565"));
member.getAddressHistory().add(new Address("old2", "street", "65555565"));

em.persist(member);

tx.commit();

쿼리를 보자!

  • 처음 값 타입인 address가 Member에 insert됨
    • Hibernate: 
          /* insert hellojpa.Member
              */ insert 
              into
                  Member
                  (city, street, zipcode, endDate, startDate, TEAM_ID, USERNAME, MEMBER_ID) 
              values
                  (?, ?, ?, ?, ?, ?, ?, ?)
  • 이후 insert collection이 실행됨.
    • ADDRESS에 두 번
      Hibernate: 
          /* insert collection
              row hellojpa.Member.addressHistory */ insert 
              into
                  ADDRESS
                  (MEMBER_ID, city, street, zipcode) 
              values
                  (?, ?, ?, ?)
      
      Hibernate: 
          /* insert collection
              row hellojpa.Member.addressHistory */ insert 
              into
                  ADDRESS
                  (MEMBER_ID, city, street, zipcode) 
              values
                  (?, ?, ?, ?)​
    • FAVORITE_FOOD에 세 번
      Hibernate: 
          /* insert collection
              row hellojpa.Member.favoriteFoods */ insert 
              into
                  FAVORITE_FOOD
                  (MEMBER_ID, FOOD_NAME) 
              values
                  (?, ?)
      
      Hibernate: 
          /* insert collection
              row hellojpa.Member.favoriteFoods */ insert 
              into
                  FAVORITE_FOOD
                  (MEMBER_ID, FOOD_NAME) 
              values
                  (?, ?)
      
      Hibernate: 
          /* insert collection
              row hellojpa.Member.favoriteFoods */ insert 
              into
                  FAVORITE_FOOD
                  (MEMBER_ID, FOOD_NAME) 
              values
                  (?, ?)​
    • em.persist(member)하나로 쿼리들이 모두 나갔다.
    • 매핑이 잘 되었당 DB 모두 반영되었다.

 

 

 

5. 값 타입 컬렉션 조회 예제


  • 값 타입 컬렉션도 지연 로딩 전략 사용해야함.
  • 영속성 컨텍스트를 싹 비우고 member를 조회해 보자.
  • Member member = new Member();
    member.setUserName("member1");
    //address 값타입
    member.setHomeAddress(new Address("homeCity", "street", "sdd2d"));
    
    //값타입 컬렉션
    member.getFavoriteFoods().add("치킨");
    member.getFavoriteFoods().add("족발");
    member.getFavoriteFoods().add("피쟈");
    
    member.getAddressHistory().add(new Address("old1", "street", "65555565"));
    member.getAddressHistory().add(new Address("old2", "street", "65555565"));
    
    em.persist(member);
    
    em.flush();
    em.clear();
    
    System.out.println("===============START=============");
    Member findMember = em.find(Member.class, member.getId());
    
    tx.commit();
  • ? Member에 대해서만 Select 쿼리가 실행되었다.
  • 즉, 값 타입 컬렉션에 대해서는 지연로딩이 적용되고 있는 것이다.
  • 반면에 임베디드타입은 바로 불러옴 ㅋㅋㅋ
    ===============START=============
    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_.endDate as enddate5_6_0_,
            member0_.startDate as startdat6_6_0_,
            member0_.TEAM_ID as team_id8_6_0_,
            member0_.USERNAME as username7_6_0_ 
        from
            Member member0_ 
        where
            member0_.MEMBER_ID=?​
  • 컬렉션을 사용할 때 쿼리가 나가게됨.
    Member member = new Member();
    member.setUserName("member1");
    //address 값타입
    member.setHomeAddress(new Address("homeCity", "street", "sdd2d"));
    
    //값타입 컬렉션
    member.getFavoriteFoods().add("치킨");
    member.getFavoriteFoods().add("족발");
    member.getFavoriteFoods().add("피쟈");
    
    member.getAddressHistory().add(new Address("old1", "street", "65555565"));
    member.getAddressHistory().add(new Address("old2", "street", "65555565"));
    
    em.persist(member);
    
    em.flush();
    em.clear();
    
    System.out.println("===============START=============");
    Member findMember = em.find(Member.class, member.getId());
    
    System.out.println("===============ADDRESS=============");
    List<Address> addressHistory = findMember.getAddressHistory();
    for (Address address : addressHistory) {
    
    System.out.println("address = " + address);
    }
    
    System.out.println("===============FOOD=============");
    Set<String> favoriteFoods = findMember.getFavoriteFoods();
    for (String favoriteFood : favoriteFoods) {
    System.out.println("favoriteFood = " + favoriteFood);
    }
    
    tx.commit();
  • ===============ADDRESS=============
    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=?
    
    address = hellojpa.Address@8dfd80a4
    address = hellojpa.Address@8dfd8465
    ===============FOOD=============
    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=?
    favoriteFood = 족발
    favoriteFood = 피쟈
    favoriteFood = 치킨

 

 

 

6. 값 타입(컬렉션) 수정


  • 값 타입은 불변하다. IMMUTABLE
  • 따라서 값 타입 수정하기 위해서는 생성자를 통해 새로 만들고 넣어줘야 한다.
    Address a = new Address("new city", "new Street", "new zip code");
    findMember.setHomeAddress(a);​
  • String은 업데이트가 안된다. 지웠다가 다시 넣어줘야 한다. String도 값 타입이다.
    //치킨 -> 한식으로 바꾸고픔
    //String 은 갈아 끼워야함 ㅋㅋㅋㅋ
    findMember.getFavoriteFoods().remove("치킨");
    findMember.getFavoriteFoods().add("한식");​
  • 값 타입 컬렉션은 어떻게??? 똑같이 지웠다가 새로 넣어줘야 한다.
    • 다만 컬렉션은 일반적으로 remove 등 대상을 찾을 때 equals()를 사용한다.
    • 따라서 equals(), hash()가 override되어 있어야 정상적으로 사용할 수 있다.
    • 값 타입은 동일성 비교가 아니라 동등성 비교를 해야하기 때문.
      //주소 히스토리 바꾸자
      //기본적으로 컬렉션은 대상을 찾을 때 equals를 사용함.
      //Address에 equals()를 override했기 때문에 동등성 비교를 시행함.
      findMember.getAddressHistory().remove(new Address("old1", "street", "65555565"));
      findMember.getAddressHistory().add(new Address("new1", "street", "65555565"));
    • 그런데 remove를 하면 member과 관련된 Address_History 테이블이 전부 삭제되었다가 insert 된다..ㅠ
    • 결과적으로는 의도한대로 입력이 되긴 했는데.. --> 6번으로 가자.

 

 

7. 값 타입 컬렉션 사용.


  • 값 타입의 라이프사이클이 엔티티를 따라가는 것 처럼
  • 값 탕입 컬렉션도 라이프사이클이 엔티티를 따라간다.
  • 즉, 값타입 컬렉션은 CASCADE  + 고아객체 제거 기능을 필수로 가져간다고 볼 수 있다.+

 

8. 값 타입 컬렉션의 제약사항


  • 값 타입은 엔티티와 다르게 식별자 개념이 없다.
  • 값은 변경하면 추적이 어렵다.
  • 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고,
    값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
  • 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야 함
    • null 입력 안되고 중복도 안됨.
  • 쓰지마 ㅋㅋㅋㅋㅋㅋㅋㅋ
  • 값 타입 컬렉션의 테이블은 보면 ID가 없어. 추적도 안됨.
  • 이렇게 복잡하게 쓸거면 하지마 그냥 다르게할거야 ㅋㅋㅋㅋ🤣

 

 

9. 값 타입 컬렉션 대안


  • 실무에서는 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려. 훨씬 편한데..?
  • 일대다 관계를 위한 엔티티를 만들고, 여기에서! 값 타입을 활용하면 됨
  • CASCADE + 고아 객체 제거를 사용해서 값타입 컬렉션 처럼 사용하면됨.
  • AddressEntity 
    @Entity
    @Table(name = "ADDRESS")
    public class AddressEntity {
    
        @Id @GeneratedValue
        private Long id;
    
        private Address address;
    
    }​
  • Member
    //일대다 단방향 매핑.
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "MEMBER_ID")
    private List<AddressEntity> addressHistory = new ArrayList<>();
  • Test
    깔끔하게 엔티티로 저장할 수 있다.
    Member member = new Member();
    member.setUserName("member1");
    //address 값타입
    member.setHomeAddress(new Address("homeCity", "street", "sdd2d"));
    
    //값타입 컬렉션
    member.getFavoriteFoods().add("치킨");
    member.getFavoriteFoods().add("족발");
    member.getFavoriteFoods().add("피쟈");
    
    member.getAddressHistory().add(new AddressEntity("city", "street", "zipcode"));
    member.getAddressHistory().add(new AddressEntity("city2", "street", "zipcode"));
    
    em.persist(member);
    
    em.flush();
    em.clear();
    
    tx.commit();​

 

10. 정리


  • 엔티티 타입의 특징
    • 식별자 있음
    • 생명 주기 관리
    • 공유됨.(참조값)
  • 값 타입의 특징
    • 식별자 없음
    • 생명 주기를 엔티티에 의존
    • 공유하지 않는 것이 안전(새로 만들어서 값을 복사해서 사용)
    • 불변 객체로 만들어.
  • 값 타입은 정~말 값 타입이라 판단될 경우에만 사용하자.
  • 엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안됨
  • 식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면!! 값 타입이 아닌 엔티티임.

 

 

11. GitHub : 210813 값 타입 컬렉션


 

GitHub - bsh6463/JPA

Contribute to bsh6463/JPA development by creating an account on GitHub.

github.com

 

'인프런 > [인프런] 자바ORM 표준 JPA 프로그래밍' 카테고리의 다른 글

객체지향 쿼리 언어  (0) 2021.08.14
[예제] 값 타입 매핑  (0) 2021.08.14
값 타입 비교  (0) 2021.08.13
값 타입과 불변 객체  (0) 2021.08.13
임베디드 타입  (0) 2021.08.12
Comments