[JPA] 값 타입 컬렉션

19·2022년 10월 19일
0

JPA

목록 보기
16/18

값 타입 컬렉션

값 타입을 컬렉션에 담아서 사용하는 것을 의미한다
값 타입을 하나 이상 저장할 때 사용한다
영속성 전이(CASCADE) + 고아 객체 제거 기능을 가지는 느낌

  • 상위 엔티티를 통해서 하위 엔티티의 생명주기를 관리
    지연 로딩 전략을 사용한다

  • 테이블에는 컬렉션이라는 개념이 없기 때문에, 1대다 관계로 풀어낸다

사용 예시

코드)

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<>();
    ...
}
  • @ElementCollection, @CollectionTable을 사용해 매핑한다.
  • 컬렉션을 저장하기 위한 별도의 테이블을 생성한다.

  • 컬렉션을 저장하기 위한 테이블 (FAVORITE_FOOD, ADDRESS)가 생성되었다

데이터 저장 예시)

Member member = new Member();

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

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

em.persist(member);
  • member만 persist했다.

  • member만 persist했지만, 값 타입 컬렉션에 데이터가 삽입되는 것을 확인할 수 있다
    • 값 타입 컬렉션은 member와는 다른 별도의 테이블이지만 생명주기가 member에 소속되기 때문에 별도로 persist하지 않아도, member를 persist하면서 데이터가 삽입된다.

데이터 조회 예시)

System.out.println("=======START========");
Member findMember = em.find(Member.class, 1L);
  • member를 조회

  • 값 타입 컬렉션의 기본 전략이 지연로딩이기 때문에, 조회되지 않는다.
    • FAVORITE_FOOD, ADDRESS

실제로 사용될 때, DB로 쿼리가 날라간다.

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

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

Set<String> favoriteFoods = findMember.getFavoriteFoods();
for (String favoriteFood : favoriteFoods) {
    System.out.println("favoriteFood = " + favoriteFood);
}
  • member를 조회하고, member에서 값 타입 컬렉션을 조회했다.

  • 값 타입 컬렉션을 조회하는 쿼리가 날라가는 것을 확인할 수 있다

데이터 수정 예시)

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

// 치킨 -> 한식, 아예 지우고 새로운 값을 넣어야 한다. (수정할 수 있는 개념이 아님)
findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");

// 먼저 기존 객체를 지워야 한다.  주의할 것은 Address에 equals(), hashcode() 메소드가 재정의되어 있어야 함!! 안되어있으면 삭제가 안 됨
findMember.getAddressHistory().remove(new Address("old1", "street", "10000"));  // 기존 객체
findMember.getAddressHistory().add(new Address("newCity1", "street", "10000"));
  • 값 타입은 불변이어야 하므로, 수정해서 사용하는 것이 아니라 아예 새로운 것으로 대체되어야 한다

  • FAVORITE_FOOD 부분

  • ADDRESS 부분

  • 쿼리를 잘 보면, ADDRESS 테이블 싹 비웠다가 다시 데이터를 넣는 것을 확인할 수 있다.

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

    • 실무에서는 상황에 따라 값 타입 컬렉션 대신 일대다 관계/다대일 양방향 관계를 고려한다
    • 일대다 관계를 위한 엔티티를 만들고, 여기에서 값 타입을 사용한다
    @Entity
    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);
       }
    }
    
    @Entity
    class Member {
        ...
        // 값 타입 매핑에서 엔티티 매핑으로 변경
        
        //@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<>();
        ...
    }
    • 영속성 전이 + 고아 객체를 사용해서 값 타입 컬렉션처럼 사용한다
    • 엔티티이기 때문에 수정이 가능

값 타입 -> 엔티티 타입으로 변경 후, 데이터 삽입

findMember.getAddressHistory().add(new AddressEntity("old1", "street", "10000"));
findMember.getAddressHistory().add(new AddressEntity("newCity1", "street", "10000"));

  • DB에 기본키값이 생겼다

그럼 값 타입 컬렉션을 언제 쓰나?

진짜 단순할 때 쓴다
추적할 필요가 없고, 값이 바뀌어도 업데이트할 필요가 없을 때

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

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



참고

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 김영한

profile
하나씩 차근차근

0개의 댓글