@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
private String name;
//기간
@Embedded
private Period period;
//주소
@Embedded
private Address homeAddress;
@Embedded
@AttributeOverrides(value = {
@AttributeOverride(name = "zipcode",column = @Column(name = "work_zipcode")),
@AttributeOverride(name = "street",column = @Column(name = "work_street"))
})
private Address workAddress;
@OneToMany(cascade = CascadeType.ALL,orphanRemoval = true)
@JoinColumn(name = "member_id")
private List<AddressHistory> addressHistories = new ArrayList<>();
//값타입 컬랙션을 사용하기 위한 방법이다.
//테이블을 만들어낸다.
//생명주기가 자동으로 관리된다.
@ElementCollection
@CollectionTable(name= "FAVORITE_FOOD",joinColumns = @JoinColumn(name = "member_id"))
private List<String> favoriteFoods = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
}
JPA에서 엔티티에서 값으로만 사용되는 타입들을 값 타입이라 한다. 위의 코드를 예를 들면 private Team team;
필드를 제외한 모든 필드는 값 타입이라 할 수 있다.
1. 값 타입 컬랙션
DB에서는 컬랙션을 담는 필드 만들 수 없다 , String 이나 Integer Object 자바 객체들을 담는 컬랙션을
DB로 표현하기위해서 JPA는 컬랙션에 대한 테이블을 만들어서 부모테이블과 연관 관계를 맺어 사용하게 한다.
@ElementCollection
@CollectionTable(name = "테이블 이름",joinColumns = @JoinColumn(name="member_id"))
위의 두 어노테이션을 지정해준다 .
@ElementCollection은 기본 컬랙션 타입임을 선언하고
@CollectionTable은 만들어질 테이블의 이름과 외래 키를 정하는 어노테이션이다 .
프로그램을 실행하면
위와 같은 테이블이 생성된다
위 방법의 문제는 해당 값을 삭제할 때 발생한다 .
Member member = new Member();
member.setName("member1");
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("피자");
member.getFavoriteFoods().add("고기");
em.flush();
em.clear();
Member member1 = em.find(Member.class, member.getId());
//값 타입 리스트 테이블을 모두 지우고 다시 넣는다 .
member1.getFavoriteFoods().remove("피자");
favoriteFoods에 '치킨',피자','고기'을 추가하고 저장 후에 '피자'라는 값을 지워봤다.
쿼리를 살펴보자
? 멤버 favorit_food 테이블을 몽땅 지워버리고 값을 다시 넣었다 .
값 타입 컬랙션은 DB에서 테이블로 표현 된다고 했다 . 생성된 테이블은 PK값을 따로 갖고 있지 않다 .
참조하는 member의 외래키 값만 가지고 있기 때문에 JPA에서는 값을 지우려해도 그 값의 고유키가 없어 찾을 방법이 없기 때문에
해당 테이블을 모두 지워버리고 그 remove() 된 레코드를 뺀 테이블에 원래 있던 레코드들을 다시 집어넣는다 .
위의 경우 3개가 들어 있었고 , 1개를 뻈으니 테이블이 지워진 후 남아있던 2개가 다시 채워진 것이다 .
이 같은 문제는 값 타입이기 때문에 발생하는 문제다 처음에 말했듯이 값 타입은 스스로 생명주기를 갖지 못하고 , 값의 변경에 대한 추적이
불가능 하기 떄문이다.
때문에 정말 간단한 데이터를 담는 것이 아닌 이상 이 방법으로는 사용하지 않는 것이 좋다 .
만약 테이블에 100개가 들어있다고 하면 , 1개를 지울때 인서트문이 100개가 나갈 수 있다 .
2.값 타입 컬렉션 문제 해결
값 타입 컬렉션의 문제를 해결하는 방법은 해당 값타입을 담을 엔티티를 만들어 랩핑하는 방법이 있다 .
회원의 주소 이력을 담는 AdressHistory라는 엔티티를 만들었다 .
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "address")
public class AddressHistory {
@Id
@GeneratedValue
private Long id;
@Embedded
private Address address;
public AddressHistory(Address address) {
this.address = address;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AddressHistory that = (AddressHistory) o;
return Objects.equals(id, that.id) && Objects.equals(address, that.address);
}
@Override
public int hashCode() {
return Objects.hash(id, address);
}
}
아이디 값과 임베디드 타입의 Address를 담고 있다 . 값 타입 컬랙션안의 요소들의 삭제를 위해선 비교를 위한
Equals 코드와 Hash를 오버라이드 하는 것이 필수다 .(list.get.remove() <- 메서드는 equals메서드를 통해 값을 찾아냄)
이제 Address 가 엔티티로 관리될 것이다.
맨위의 Member 엔티티 클래스를 보면 알 수 있듯이 1대다 단방향으로 맵핑이 되어있다 . 1대다 단방향의 특성상 외래키를 AddressHistory에서 관리하기 떄문에 update 문이 생기는 건 피할 수 없다.
엔티티 타입으로 랩핑 됬지만 값타입의 특성을 지키기 위해선 @OneToMany(cascade = CascadeType.ALL,orphanRemoval = true) 처럼 생명주기를 부모객체인 멤버에 맡기는 것이 좋다.
결론은 값 타입은 진짜로 간단한 데이터에만 사용하던지
사용해야한다면 엔티티로 랩핑하여 사용하는 것이 좋을 것 같다.
'JPA' 카테고리의 다른 글
JPA 공부 14 - JPQL . 1 (0) | 2021.06.06 |
---|---|
JPA 공부 13 - 직접 쿼리를 작성하는 방법 (0) | 2021.06.06 |
JPA 공부 11 - JPA 데이터 타입 분류 (임베디드 타입) (0) | 2021.06.05 |
JPA 공부 10 - 영속성 전이 (cascade) (0) | 2021.06.05 |
JPA 공부 9 - 즉시로딩과 지연로딩 (0) | 2021.06.05 |