목표
값 타입 컬렉션에 대해 알아보고 이를 코드를 통해 저장, 조회, 수정해본다.
값 타입 컬렉션
- 값 타입을 하나 이상 저장할 때 사용한다.
- @ElementCollection, @CollectionTable을 사용한다.
- 데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.
- 컬렉션을 저장하기 위한 별도의 테이블이 필요하다.
(일대다 등의 매핑을 통해 서로 연관되도록 할 것임)
@Entity @Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name="MEMBER_ID")
private Long id;
@Column(name="USERNAME")
private String username;
...(생략)...
@ElementCollection
@CollectionTable(name="FAVORITE_FOOD", joinColumns = @JoinColumn(name="MEMBER_ID"))
@Column(name="FOOD_NAME")
private Set<String> favortieFoods = new HashSet<>();
@ElementCollection
@CollectionTable(name="ADDRESS", joinColumns = @JoinColumn(name="MEMBER_ID"))
private List<Address> addressHistory = new ArrayList<>();
...(생략)...
}
ADDRESS, FAVORITE_FOOD 둘 다 MEMBER_ID가 있어야 어떤 멤버에 소속되어 있는지 알 수 있기 때문에 필요하다.
값 타입 컬렉션 사용
1) 값 타입 저장 예제
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","100000"));
member.getFavortieFoods().add("치킨");
member.getFavortieFoods().add("족발");
member.getFavortieFoods().add("피자");
member.getAddressHistory().add(new Address("old1","street","100000"));
member.getAddressHistory().add(new Address("old1","street","100000"));
em.persist(member);
tx.commit();
}catch(Exception e){
tx.rollback();
}finally {
em.close();
}
emf.close();
}
위 코드에서 em.persist 한번에 member, favoriteFoods, address 모두가 persist 된다.
값 타입 컬렉션들은 별도의 테이블에 있는데도 불구하고 member에 의존하기 때문에 생명주기가 같다.
따라서 값 타입들은 별도로 persist 해줄 필요가 없다.
-> 값 타입 컬렉션은 영속성전이 + 고아 객체 제거 기능을 필수로 가진다고 볼 수 있다.
2) 값 타입 조회 예제
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","100000"));
member.getFavortieFoods().add("치킨");
member.getFavortieFoods().add("족발");
member.getFavortieFoods().add("피자");
member.getAddressHistory().add(new Address("old1","street","100000"));
member.getAddressHistory().add(new Address("old1","street","100000"));
em.persist(member);
em.flush();
em.clear();
Member findMember = em.find(Member.class, member.getId()); // member 조회
tx.commit();
}catch(Exception e){
tx.rollback();
}finally {
em.close();
}
emf.close();
}
값 타입 컬렉션도 지연 로딩 전략을 사용한다.(기본 값이 fetch=LAZY)
따라서 해당 값 타입을 사용하기 전까지는 호출을 하지 않는다.
Address와 Favorite_Food 관련 필드는 select하지 않음을 알 수 있다.
위의 city, street, zipcode는 값 객체가 아닌 Embeded 객체에서 호출된 필드로 다른 것이다.
3) 값 타입 수정 예제
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","100000"));
member.getFavortieFoods().add("치킨");
member.getFavortieFoods().add("족발");
member.getFavortieFoods().add("피자");
member.getAddressHistory().add(new Address("old1","street","100000"));
member.getAddressHistory().add(new Address("old1","street","100000"));
em.persist(member);
em.flush();
em.clear();
Member findMember = em.find(Member.class, member.getId()); // member 조회
findMember.setHomeAddress(new Address("newCity", a.getStreet(), a.getZipcode()));
//값타입 치킨-> 한식 수정
findMember.getFavortieFoods().remove("치킨");
findMember.getFavortieFoods().add("한식");
//값타입 주소 변경(equals 메소드를 제대로 재정의 해주었을 경우 remove가 잘 동작한다)
findMember.getAddressHistory().remove(new Address("old1","street","100000"));
findMember.getAddressHistory().add(new Address("newCity1","street","100000"));
tx.commit();
}catch(Exception e){
tx.rollback();
}finally {
em.close();
}
emf.close();
}
값 타입을 수정할 때는, 해당 값 타입을 삭제해주고 다시 추가해주는 방식을 사용한다.
필드가 여러개인 객체를 삭제할 때는 반드시 equals 메소드를 재정의해준 상태어야 한다.
그렇지 않으면 값이 제대로 삭제가 되지 않아 문제가 발생하기 때문이다.
값 타입 컬렉션의 제약사항
- 값 타입은 엔티티와 다르게 식별자 개념이 없어 변경하면 추적이 어렵다.
- 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고,
값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
==> 따라서 굉장히 비효율적, 실무에서 잘 안씀
- 값타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성한다. (따라서 null, 중복 저장 금지)
값 타입 컬렉션 대안
- 실무에서는 값 타입 컬렉션 대신에 일대다 관계를 고려한다.
(쿼리 최적화도 유리, null 값 가질수도 있음, 수정 편리)
따라서 진짜 단순하고 추적할 필요없는 데이터, 또 업데이트할 필요가 없는 값에 값타입 컬렉션을 사용한다.
다른 경우, 특히 식별자가 필요하고 지속해서 값을 추적, 변경해야 한다면 그것은 값 타입이 아닌 엔티티를
사용한다.
엔티티 타입 | 값 타입 | |
식별자 | 필요함 | 필요없음 |
생명 주기 | 스스로 주기 관리 | 엔티티에 의존 |
공유 가능 여부 | 엔티티 공유 가능 | 공유하지 않는 것이 안전(복사해서 사용) |
기타 | 불변 객체로 만드는 것이 안전 |
'JPA' 카테고리의 다른 글
[JPA] 값 타입 비교 (0) | 2023.07.21 |
---|---|
[JPA] 값 타입과 불변 객체 (0) | 2023.07.21 |
[JPA] 임베디드 타입 (0) | 2023.07.21 |
[JPA] 영속성 전이(CASCADE)와 고아 객체 (0) | 2023.07.17 |
[JPA] 프록시와 즉시/지연 로딩 (0) | 2023.07.17 |