참고
JPA 데이터 타입
NOTE
JPA의 데이터 타입을 가장 크게 분류하면 엔티티 타입과 값 타입으로 나눌 수 있다.
엔티티 타입의 특징
•
식별자가 있다
•
생명 주기 관리(값 타입은 생명주기 관리를 주도적으로 할 수 없다.)
•
공유
값 타입의 특징
•
식별자가 없다
•
생명 주기를 엔티티에 의존
•
공유하지 않는 것이 안전(복사해서 사용)
•
불변 객체로 만드는 것이 안전
기본값 타입
NOTE
int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
private int age;
//..
}
Java
복사
•
Member엔티티의 값 타입인 name, age 속성은 기본값 타입이다.
◦
생명주기가 Member엔티티에 의존된다.
•
엔티티를 제거하면 name과 age또한 제거된다.
임베디드 타입(복합 값 타입)
NOTE
새로운 값 타입을 정의해서 사용할 수 있는데, JPA에서는 이것을 임베디드 타입이라 한다.
임베디드 타입을 통해 객체를 분리해도 테이블은 하나만 맵핑된다.
@Entity
public class Member {
//..
@Embedded
private Period period;
@Embedded
private Address address;
}
Java
복사
@Embeddable
@Getter @Setter
public class Period {
private LocalDateTime startDate;
private LocalDateTime endDate;
}
Java
복사
사용 어노테이션
•
@Embeddable : 값 타입을 정의하는 곳에 표시
•
@Embedded : 값 타입을 사용하는 곳에 표시
특징
•
임베디드 타입을 포함한 모든 값 타입은 엔티티의 생명주기에 의존하므로 타입의 관계를 UML로 표현하면 컴포지션 관계가 된다.
•
임베디드 타입으로 객체와 테이블을 아주 세밀하게 맵핑할 수 있으며, 잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많다.
@AttributeOverride: 속성 재정의
임베디드 타입과 테이블 매핑
NOTE
엔티티 - 임베디드 타입 - 엔티티가 가능하다.
•
Member엔티티는, Address임베디드 타입을 가지고, Address임베디드 타입또한 ZipCode값 타입을 가질 수 있다.
•
Member엔티티는 PhoneNumber임베디드 타입을 가지고, PhoneNumber도 PhoneEntity엔티티를 가질 수 있다.
•
@AttributeOverride: 속성 재정의
NOTE
•
만약 2개 이상의 동일한 임베디드 타입을 사용하려면 @AttributeOverride를 사용하면 된다.
@Entity
public class Member {
@Embedded
Address homeAddress;
@Embedded
@AttributeOverrides({
@AttributeOverride(name="city", column=@Column(name = "COMPANY_CITY")),
@AttributeOverride(name="street", column=@Column(name = "COMPANY_STREET")),
@AttributeOverride (name="zipcode", column=@Column (name = "COMPANY_ZIPCODE"))
})
Address companyAddress;
}
Java
복사
◦
동일 임베디드 타입을 사용하므로, 컬럼명을 전부 다르게 맵핑해줘야 한다.
값 타입과 불변 객체
값 타입 공유 참조문제
NOTE
임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험하다.
두 엔티티가 동일 주소를 참조한다.
Address address = new Address("city", "street", "10000");
Member member1 = new Member();
member.setAddress(address);
Member member2 = new Member();
member.setAddress(address); // member1의 address 공유참조
member1.getHomeAddress().setCity("newCity"); // member1의 address 수정
em.persist(member1);
em.persist(member2);
Java
복사
member2도 같이 바뀌어버림
값 타입 공유 참조 - 해결법
NOTE
1. 값 복사해서 사용하기
Address address = new Address("city", "street", "10000");
Address copyAddress = new Address(address.getCity(),
address.getStreet(),
address.getZipcode());
Java
복사
•
위와같이, 새로운 member에 address를할당하기 위해 새로운 복사객체를 하나 만든다.
2. 값 수정을 못하게 막아버린다.
•
객체 타입 수정자체를 틀어막아서 부작용을 피할 수 있다
•
생성자로만 값을 설정, 수정자(Setter)를 만들지 않으면 된다.
•
값을 변경해야 하는 경우 새로운 객체를 만들어서 넣어준다.
값 타입 비교
NOTE
•
equals() 메소드와 hasCode()를 오버라이딩해서 비교한다.
•
자세한건 참고노트 참조.
값 타입 컬렉션
NOTE
값 타입을 하나 이상 저장할 때 사용한다.
콜렉션은 따로 테이블이 생성된다.
@ElementCollection
@CollectionTable(name = "FAVORITE_FOOD", joinColumns = @JoinColumn(name="MEMBER_ID")
@Column(name = "FOOD_NAME")
private Set<String> favoriteFoods = new HashSet<>();
@ElementCollection
@CollectionTable(name = "addressHistory", joinColumns = @JoinColumn(name="MEMBER_ID")
@Column(name = "ADDRESS")
private List<Address> addressHistory = new ArrayList<>();
Java
복사
•
@ElementColliection, @CollectionTable 사용
•
데이터베이스는 컬렉션을 같은 테이블에 저장할 수 있다.
•
컬렉션을 저장하기 위한 별도의 테이블이 필요해진다.
사용방법
Member findMember = em.find(Member.class, 1L);
/*
SQL: select MEMBER_ID, street, zipcode, USERNAME from MEMBER
where MEMBER_ID = 1L;
*/
Java
복사
조회 예제
•
값 타입 컬렉션은 지연 로딩 전략을 사용한다.
/*기본적인 임베디드 타입 변경*/
findMember.getHomeAddress().setCity("newCity");// 잘못된 수정
Address add = findMember.getHomeAddress();
findMember.setHomeAddress(new Address("newCity", add.getStreet(), add.getZipcode()));
/*값 타입 컬렉션 수정 예제 - 치킨을 김밥으로 변경*/
findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("김밥");
findMember.getAddressHistory().remove(new Address("old1", "street", "10000"));
findMember.getAddressHistory().add(new Address("newCity", "street", "10000"));
Java
복사
•
값 타입 컬렉션들은 생명주기를 소유객체에 의존한다.
◦
여기서 값 타입 실행쿼리를 보면, 기존 데이터 삭제 → 신규 데이터 추가가 아닌, 값타입 컬렉션 데이터 전체가 갈아끼워진다
◦
ex) 10개의 데이터가 모두 지워지고 데이터 2개를 추가하는 방식
값 타입 컬렉션 - 문제점
NOTE
값 타입 컬렉션은 실무에선 사용하지 않는걸 권장한다.
사용하지 않는 이유
•
값 타입은 엔티티와 다르게 식별자 개념이 없다. (PK)
•
값을 변경하면 추적이 어렵다.
•
변경 사항이 발생하면, 주요 엔티티와 연관된 엔티티의 모든 데이터를 삭제하고 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
•
내용이 정말 간단한 경우에는 사용해도 무방
실무에서 사용하는 방식
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "MEMBER_ID")
private List<AddressEntity> addressHistory = new ArrayList<>();
Java
복사
•
실무에서는 상황에 따른 값 타입 컬렉션 대신 일대다 관계를 고려한다.
•
영속성 전이 + 고아 객체 제거를 사용해서 값 타입 컬렉션 처럼 사용한다.