값 타입
참고 이글은 자바 ORM 표준 JPA프로그래밍을 읽고 정리한 글입니다.
JPA데이터 타입은 크게 분류해서 2가지로 나눌 수 있다.
1.엔티티 타입
@Entity로 정의하는 객체
식별자 o -> 추적 o
살아있는 생물.
2.값 타입
int,Integer,String처럼 단순히 값으로 사용하는 자바 기본타입이나 객체.
식별자x -> 추적x
단순한 수치 정보.
값타입
값타입은 다시 3개로 나눌 수 있다.
1.기본값 타입
자바 기본타입 , 래퍼 클래스, String
2.임베디드 타입
Jpa에서 사용자가 직접 정의한 값 타입.
3.컬렉션 타입
하나 이상의 값 타입을 저장할때 사용.
기본값타입
- 식별자 값도 없고 생명주기도 엔티티에 의존한다.
- 공유x
임베디드타입
- 새로운 값 타입을 직접 정의해서 사용 -> 직접 정의해도 결국 값 타입
- 새로 정의한 값타입을 재 사용가능 , 응집도 높음
-@Embeddable:값타입을 정의하는 곳에 사용. @Embedded : 값 타입을 사용하는 곳에 표시.
위의 두 어노테이션 필요. 단 한가지 생략 가능.
- 기본 생성자가 필수.
- 모든 값타입은 엔티티의 생명주기에 의존 하기때문에 UML로 표시하면 컴포지션 관계.
ㄴ 하이버네이트는 임베디드 타입을 컴포넌트라고 한다.
- 임베디드 타입이 null이면 매핑한 컬럼 값은 모두 null.
임베디드 타입과 테이블 매핑
임베디드 타입도 엔티티의 값일 뿐. -> 매핑하는 테이블은 임베디드타입을 쓰든 안쓰든 같다.
임베디드 타입을 사용하면 아주 세밀하게 매핑 가능.
-> 잘 설계한 ORM 애플리케이션은 매핑한 테이블보다 클래스가 더 많다.
임베디드 타입과 연관관계
임베디드 타입은 값 타입을 포함하거나 엔티티를 참조할 수 있다.
임베디드 타입은 임베디드 타입을 가질 수 있다.
임베디드 타입은 엔티티 타입을 참조할 수 있다.
@AttributeOverride: 속성 재정의
한 엔티티에 똑같은 임베디드 타입을 2개이상 사용할 시에는 테이블에 매핑하는 컬럼명이 중복되기 때문에 매핑 정보를 재정의 해줘야한다. 이럴때 사용하는 어노테이션이 @AttributeOverride이다.
@Entity
public class Member {
@Embedded
private Address homeAddress;
@Embedded
@AttributeOverrides({ // 새로운 컬럼에 저장 (컬럼명 속성 재정의)
@AttributeOverride(name="city", column=@Column(name = "WORK_CITY"),
@AttributeOverride(name="street", column=@Column(name = "WORK_STREET"),
@AttributeOverride(name="zipcode", column=@Column(name = "WORK_ZIPCODE")})
}
@AttributeOverride를 사용하면 어노테이션을 너무 많이 사용해서 엔티티 코드가 지저분해진다. 하지만 한 엔티티에 같은 임베디드 타입을 중복해서 사용하는일은 많지않다.
AttributeOverride속성은 무조건 엔티티에서 설정.
값타입과 불변 객체.
값타입은 복잡한 객체 세상을 조금이라도 단순화하려고 만든 개념.
-> 값타입은 단순하고 안전하게 다뤄야 함.
값타입 공유 참조 문제점
임베디드 타입 같은 값 타입은 여러 엔티티에서 공유하면 위험함
-> 이러한 문제점 부작용 발생 -> 해결 방안 값을 복사해서 사용
값타입 복사
값타입의 실제 인스턴스인 값을 공유하는 것은 위험하다. 대신에 값을 복사해서 사용해야 한다.
값을 항상 복사하면 공유참조로 인한 부작용을 피할 수 있다.
여기서 또다른 문제가 일어난다. 임베디드 타입 처럼 직접 값을 정의한 타입을 자바에서는 기본적으로 객체 타입으로 생성한다. 객체 타입의 경우 값을 대입하면 항상 참조 값을 전달한다.
문제점은 여기서 원본의 참조 값을 직접 넘기는 것을 막을 방법이 없다는 것이다.
-> 자바는 대입할때 값 타입의 종류를 따지지 않고 객체이면 참조를 넘기고 아니면 값을 넘기는 방식임.
따라서 공유참조를 막을 다른 방법을 생각해야한다. 가장 단순한 방법으로 객체의 값을 수정하지 못하게 막으면 된다.
불변 객체
한번 만들면 절대 변경할 수 없는 객체.
위와같은 문제점으로 만들어진게 불변 객체이다. 값을 조회할 수 있지만 수정할 방법을 없애는 거다. 불변객체도 객체이기에 결국 참조를 피할 수 없지만 참조를 하더라도 인스턴스의 값을 수정할 수가 없기 때문에 부작용이 나타나지 않는다.
만드는 방법은 간단하다. 생성자만 만들고 setter()와 같은 수정자를 만들지 않으면 된다.
값 타입의 비교
자바의 객체 비교방법은 2가지가 있다
1.동일성 비교 : 인스턴스 참조 값을 비교, == 사용
2.동등성 비교 : 인스턴스의 값을 비교, equals() 사용
Address address1 = new Address("서울시");
Address address2 = new Address("서울시");
이러한 두 객체가 있다면 a==b로 동일성으로 비교하면 서로 다른 인스턴스이므로 결과는 거짓입니다. 우리가 바라는 비교는 동등성 비교이므로 a.equals(b)가 트루가 나오게 할려면 Address의 equals를 재정의 해줘야 합니다.
값 타입의 equals를 재정의 할때는 보통 모든 필드의 값을 비교하도록 구현합니다.
값 타입 컬렉션
값 타입을 하나 이상 저장하려면 컬렉션에 보관하고 @ElementCollection, @CollectionTable 어노테이션을 사용하면 됩니다.
관계형 데이터베이스의 테이블은 컬럼안에 컬렉션을 포함할 수 없다 -> 별도의 테이블을 추가하여 @CollectionTable 사용해서 추가한 테이블을 매핑해야 한다.
@Entity
class Member{
@Id @GeneratedValue
private Long id;
@Embedded
private Address homeAddress;
@ElementCollection
@CollectionTable(
name = "FAVORITE_FOOD",
joinColumns = @JoinColumn(name = "member_id")
)
@Column(name = "food_name")
private List<String> favoriteFoodList = new ArrayList<>();
@ElementCollection
@CollcetionTable(
name = "ADDRESS_HISOTRY",
joinColumns = @JoinColumn(name = "member_id")
)
private List<Address> addressHistory = new ArrayList<>();
}
ADDRESS_HISTORY는 임베디드 타입인 Address를 컬렉션으로 가진다. 이것도 마찬가지로 별도의 테이블을 사용해야 한다. 그리고 테이블 매핑정보는 @AttributeOverride를 사용해서 재정의할 수 있다.
값타입 컬렉션 사용 저장
Member member = new Member();
member.setHomeAddress(new Address("부산","해운대구","660-123"));
member.getFavoriteFoodList().add("삼겹살");
member.getFavoriteFoodList().add("치킨");
member.getAddressHistory().add(new Address("부산","사상","123-123"));
member.getAddressHistory().add(new Address("부산", "덕천", "000-000"));
em.persist(member);
등록을 보면 엔티티 member만 영속화했다. JPA는 이때 member엔티티의 값 타입도 함께 저장한다. 실제 insert SQL문
member: insert 1번
member.homeDaddress:컬렉션이 아닌 임베디드 값 타입이므로 회원 테이블을 저장하는 SQL에 포함
member.favoriteFoods: inset 2번
member.addressHistory: insert 2번
총 5번 INSERT SQL문이 실행된다.
값타입 컬렉션은 영속성 전이+고아 객체 제거 기능을 필수로 가진다.
조회의 경우에는 값타입 컬렉션도 패치 전략으로 LAZY가 기본이다.
@ElementCollection(fetch = FetchType.LAZY)
값 타입 컬렉션의 제약사항
값 타입은 엔티티와 다르게 식별자 개념이 없다.
값은 변경하면 추적이 어렵다.
값 타입 컬렉션에 변경사항이 생기면 주인 엔티티와 연관된 모든 데이터를 삭제하고, 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
값 타입 매핑 컬렉션을 매핑하는 테이블은 모든 칼럼을 묶어서 기본킬르 구성해야한다. -> null입력 x 중복 저장 x
대안
실무에서는 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려.
일대다 관계를 위한 엔티티를 만들고, 여기에서 값 타입을 사용.
영속성 전이+ 고아 객체 제거를 사용해서 값타입 컬렉션처럼 사용