티스토리 뷰

값 타입 컬렉션
- 값 타입을 하나 이상 저장할 때 사용
- @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 (2) | 2024.02.08 |
값 타입의 비교 (0) | 2024.02.06 |
값 타입과 불변 객체 (0) | 2024.02.06 |
임베디드 타입 (1) | 2024.02.06 |
- Total
- Today
- Yesterday
- Intercepter
- 항해플러스
- 로그인
- JPA
- 향해플러스백엔드
- 백엔드 개발자 역량
- 인터셉터
- exception
- jpa api
- 스프링공부
- 향해99
- 리터럴
- 스프링부트
- 백엔드 개발자 공부
- 항해99
- thymleaf
- 향해플러스
- 컨트
- HTTP
- Java
- filter
- hypertexttransferprotocol
- SpringBoot
- reject
- rejectValue
- react실행
- React
- BindingResult
- ArgumentResolver
- 예외처리
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |