반응형
250x250
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 자바의 정석
- Spring
- 스프링 데이터 JPA
- 친절한 SQL 튜닝
- 데이터모델링
- 성능최적화
- 오라클
- fetch join
- 그룹함수
- 서브쿼리
- 페치조인
- @MappedSuperclass
- JPQL
- index
- 컬렉션 조회 최적화
- querydsl
- TCP/IP
- JPA
- 엔티티
- 스프링 컨테이너
- 값 타입
- 데이터베이스
- 스프링
- Spring data JPA
- 페이징
- SQL
- 정렬
- INDEX SCAN
- SQL 튜닝
- DTO
Archives
- Today
- Total
nu_s
[JPA] 값 타입 컬렉션 🐶 본문
728x90
반응형
값 타입 컬렉션
- 값 타입을 컬렉션에 담아서 쓰는 것
- RDB는 기본적으로 컬렉션을 담을 수 있는 구조가 없다.
- 그래서 컬렉션을 별도의 테이블로 뽑아서 관리한다.
Member
@Entity
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_FOODS",
joinColumns = @JoinColumn(name = "MEMBER_ID")
)
@Column(name = "FOOD_NAME")
private Set<String> favoriteFood = new HashSet<>();
@ElementCollection
@CollectionTable(name = "ADDRESS",
joinColumns = @JoinColumn(name = "MEMBER_ID")
)
private List<Address> addressHistory = new ArrayList<>();
//Getter ,Setter...
}
이렇게 실행하면
- 값 타입을 하나 이상 저장할 때 사용한다.
- @ElementCollection, @CollectionTable을 사용한다.
- 데이터베이스는 컬렉션 같은 테이블에 저장할 수 없다.
- 컬렉션을 저장하기 위한 별도의 테이블이 필요하다.
값 타입 저장
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("homeCity", "street", "zipcode"));
member.getFavoriteFood().add("치킨");
member.getFavoriteFood().add("족발");
member.getFavoriteFood().add("피자");
member.getAddressHistory().add(new Address("old1", "street", "zipcode"));
member.getAddressHistory().add(new Address("old2", "street", "zipcode"));
em.persist(member);
- 값 타입 컬렉션을 따로 persist()하지 않았는데 저장이 된 것을 볼 수 있다.
- 값 타입 컬렉션도 생명주기를 엔티티에 의존하기 때문이다.
- 참고 : 값 타입 컬렉션은 영속성 전이(CASCADE) + 고아 객체 제거 기능을 필수로 가진다는 것을 알 수 있다.
값 타입 조회
// 값 타입 저장
...
em.flush();
em.clear();
// 값 타입 조회
System.out.println("============================");
Member findMember = em.find(Member.class, member.getId());
실행 결과
============================
Hibernate:
select
member0_.MEMBER_ID as MEMBER_I1_3_0_,
member0_.city as city2_3_0_,
member0_.street as street3_3_0_,
member0_.zipcode as zipcode4_3_0_,
member0_.TEAM_ID as TEAM_ID6_3_0_,
member0_.USERNAME as USERNAME5_3_0_
from
Member member0_
where
member0_.MEMBER_ID=?
- Member를 조회했을 때
- city, street, zipcode를 보았을 때 임베디드 타입의 Address는 즉시 로딩이다. (이게 더 효율적)
- 컬렉션들은 지연 로딩이다. (FetchType fetch() default LAZY)
값 타입 수정
// 값 타입 저장
...
// 값 타입 조회
...
// 값 타입 수정
// homeCity -> newCity
//findMember.getHomeAddress().setCity("newCity"); // 부작용 발생
// 새로운 인스턴스로 갈아껴야 한다.
Address adderss = findMember.getHomeAddress();
findMember.setHomeAddress(new Address("newCity", address.getStreet(), address.getZipcode()));
// 컬렉션 수정
// 치킨 -> 한식
findMember.getFavoriteFood().remove("치킨");
findMember.getFavoriteFood().add("한식");
// 주소 old1 -> new1
// 값을 비교해서 제거하기 때문에 equals(), hashcode() 필수
findMember.getAddressHistory().remove(new Address("old1", "street", "zipcode"));
findMember.getAddressHistory().add(new Address("new1", "street", "zipcode"));
addressHistory를 수정하는 쿼리를 보면
Hibernate:
/* delete collection hellojpa.Member.addressHistory */ delete
from
ADDRESS
where
MEMBER_ID=?
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
(?, ?, ?, ?)
- UPDATE 쿼리가 아닌 DELETE, INSERT 쿼리가 나가는 것을 볼 수 있다.
- ADDRESS 테이블 전체를 DELETE하고 최종 컬렉션에 남아있는 "old2"와 "new1"을 INSERT
값 타입 컬렉션의 제약사항
- 값 타입은 엔티티와 다르게 식별자 개념이 없다.
- 값은 변경하면 추적이 어렵다.
- 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
- 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야 한다. : null 입력 X, 중복 저장 X
값 타입 컬렉션 대안책
- 실무에서는 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려한다.
- 일대다 관계를 위한 엔티티를 만들고, 여기에서 값 타입을 사용한다. (엔티티로 한번 감싼다.)
- 영속성 전이(CASCADE) + 고아 객체 제거를 사용해서 값 타입 컬렉션처럼 사용한다.
- Ex) AddressEntity
AddressEntity
@Entity
@Table(name = "ADDRESS")
public class AddressEntity {
@Id @GeneratedValue
private Long id;
private Address address;
public AddressEntity() {}
public AddressEntity(String city, String street, String zipcode) {
this.address = new Address(city, street, zipcode)
}
//Getter, Setter...
}
Member
@Entity
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_FOODS",
joinColumns = @JoinColumn(name = "MEMBER_ID")
)
@Column(name = "FOOD_NAME")
private Set<String> favoriteFood = 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<>();
//Getter ,Setter...
}
JpaMain
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("homeCity", "street", "zipcode"));
member.getFavoriteFood().add("치킨");
member.getFavoriteFood().add("족발");
member.getFavoriteFood().add("피자");
member.getAddressHistory().add(new AddressEntity("old1", "street", "zipcode"));
member.getAddressHistory().add(new AddressEntity("old2", "street", "zipcode"));
em.persist(member);
em.flush();
em.clear();
Member findMember = em.find(Member.class, member.getId());
정리
- 엔티티 타입의 특징
- 식별자 O
- 생명 주기 관리
- 공유
- 값 타입의 특징
- 식별자 X
- 생명 주기를 엔티티에 의존
- 공유하지 않는 것이 안전 (복사해서 사용)
- 불변 객체로 만드는 것이 안전
값 타입은 정말 값 타입이라 판단될 때만 사용한다.
엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안된다.
식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면 그것은 값 타입이 아닌 엔티티다.
출처 : 자바 ORM 표준 JPA 프로그래밍 - 기본편
728x90
반응형
'JPA' 카테고리의 다른 글
[JPA 활용] API 개발 (포스트맨) 🐱 (0) | 2023.10.26 |
---|---|
[JPA] JPQL 🐶 (0) | 2023.10.26 |
[JPA] 값 타입과 불변 객체 🐶 (0) | 2023.10.25 |
[JPA] 값 타입 (기본 값 타입, 임베디드 타입) 🐶 (0) | 2023.10.25 |
[JPA] 영속성 전이(CASCADE)와 고아 객체 🐶 (0) | 2023.10.21 |