JPA 스터디 (9장)
9장 값 타입
- 엔티티 타입 : @Entity 로 정의하는 객체
- 값 타입 : int, Integer, String 같이 단순히 값으로 사용하는 자바 기본 타입이나 객체
- 기본값 타입
- 자바 기본 타입 (예 : int, double)
- 래퍼 클래스 (예 : Integer)
- String
- 임베디드 타입 (복합 값 타입) : 사용자가 직접 정의한 값 타입
- 컬렉션 값 타입 : 하나 이상의 값 타입 저장 시 사용
- 기본값 타입
9.1 기본값 타입
@Entity
public class Member {
@id @GeneratedValue
private Long id;
private String name; //값 타입
private int age; //값 타입
...
}
//name, age 속성은 식별자 값도 없고 생명주기도 Membr 엔티티에 의존한다.
9.2 임베디드 타입(복합 값 타입)
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded Period workPeriod; //근무 기간
@Embedded Address homeAddress; //집 주소
}
@Embeddable
public class Period {
@Temporal(TemporalType.DATE) java.util.Date startDate;
@Temporal(TemporalType.DATE) java.util.Date endDate;
public boolean isWork(Date date) {
//값 타입을 위한 메소드 정의 가능
}
}
@Embeddable
public class Address {
@Column(name="city") //매핑 컬럼 정의
private String city;
private String street;
private String zipcode;
}
-
임베디드 타입을 포함한 모든 값 타입은 엔티티의 생명주기에 의존하므로 엔티티와 임베디드 타입의 관계를 UML로 표현하면 컴포지션 관계가 된다.
-
임베디드 타입과 테이블 매핑
- 임베디드 타입은 엔티티의 값일 뿐이므로 값이 속한 엔티티의 테이블에 매핑 한다.
- 임베디드 타입 덕에 객체와 테이블을 아주 세밀하게 매핑 가능 (주소나 근무기간 같은 값 타입 클래스를 만들어 더 객체지향적으로 개발 가능)
-
임베디드 타입과 연관관계
@Entity
public class Member {
@Embedded Address address; //임베디드 타입 포함
@Embedded PhoneNumber phoneNumber;
//...
}
@Embeddable
public class Address {
String street;
String city;
String state;
@Embedded Zipcode zipcode; //임베디드 타입 포함
}
@Embeddable
public class Zipcode {
String zip;
String plusFour;
}
@Embeddable
public class PhoneNumber {
String areaCode;
String localNumber;
@ManyToOne PhoneServiceProvider provider; //엔티티 참조
}
@Entity
public class PhoneServiceProvider {
@Id String name;
...
}
- @AttributeOverride
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded Address honeAddress;
@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;
}
//위와 같이 설정 후 auto DDL 작동 시
//COMPANY_* 의 이름을 가진 컬럼들이 생성 된다.
- 임베디드 타입과 null
- member.setAddress(null); em.persist(member);
- 회원테이블의 주소와 관련된 CITY,STREET,ZIPCODE 컬럼은 모두 null 이 된다.
9.3 값 타입과 불변 객체
-
값 타입 공유 참조
-
임베디드 타입 같은 값 타입을 여러 엔티티에서 공유 시 문제가 발생한다.
member1.setHomeAddress(new Address("OldCity")); Address address = member1.getHomeAddress(); address.setCity("NewCity"); //회원1의 address 값을 공유해서 사용 member2.setHomeAddress(address); ----------------------------------------------- 이거 회원1의 city 값도 NewCity 로 바뀌게 된다. 왜냐? 같은 인스턴스를 참조하기 때문 해결방법? 값을 복사하여 사용할 것
-
-
값 타입 복사
member1.setHomeAddress(new Address("OldCity")); Address address = member1.getHomeAddress(); Address newAddress = address.clone(); //값 복사! newAddress.setCity("NewCity"); member2.setHomeAddress(newAddress);
- 객체를 대입할 때 마다 인스턴스를 복사해서 대입 시 공유 참조를 피할 수 있는데, 문제는 복사하지 않고 원본의 참조 값을 직접 넘기는 것을 막을 방법이 없다는 것이다.
- 객체 공유 참조는 피할 수 없다. 가장 단순한 근본적인 해결책으로는 객체 값 수정을 못하게 막아버리면 된다. (Setter 를 막아버린다는 의미), Lombok 을 사용한다면 @Date 대신 @Getter만 사용 하기
-
불변 객체 (한번 만들면 절대 변경할 수 없는 객체)
- 값 타입은 부작용을 원천 차단하기 위해 될 수 있으면 불변 객체로 설계해야 한다.
- 생성자로만 값을 설정 하고 수정자를 만들지 않으면 된다! (스프링에서 생성자 주입 기법을 생각해보라!)
- 불변이라는 작은 제약으로 부작용이라는 큰 재앙을 막을 수 있다!
9.4 값 타입의 비교
- 자바가 제공 하는 객체 비교 (3장에서 잠깐 언급했던 내용)
- 동일성 : 인스턴스의 참조값을 비교, == 사용
- 동등성 : 인스턴스의 값을 비교, equals() 사용
- equals() 와 hashCode() 재정의 하여 사용! ( hashCode() 재정의 안하면?→ 해시 사용하는 컬렉션에서 문제가 발생한다)
9.5 값 타입 컬렉션
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Embedded
private Address homeAddress;
@ElementCollection //값 타입 컬렉션 이용 시 사용하는 어노테이션
@CollectionTable(name = "FAVORITE_FOODS", //값 타입 컬렉션 이용 시 사용하는 어노테이션
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<>();
...
}
@Embeddable
public class Address {
@Column
private String city;
private String street;
private String zipcode;
...
}
- 값 타입 컬렉션 사용
Member member = new Member();
//임베디드 값 타입
member.setHomeAddress(new Address("Soonworld","순해수욕장","123-4567"));
//기본값 타입 컬렉션
member.getFavoriteFoods().add("짬뽕");
member.getFavoriteFoods().add("짜장");
member.getFavoriteFoods().add("탕수육");
//임베디드 값 타입 컬렉션
member.getAddressHistory().add(new Address("서울","강남","123-1234"));
member.getAddressHistory().add(new Address("서울","강북","321-4321"));
em.persist(member);
-------------------------------------
실제 DB에 실행되는 SQL
member : INSERT 1번
member.homeAddress : 임베디드 값이므로 회원 테이블 저장 시 포함
member.favoriteFoods : INSERT 3번
member.addressHistory : INSERT 2번
//실행된 실제 SQL
INSERT INTO MEMBER(ID, CITY, STREET, ZIPCODE) VALUES(1, 'Soonworld', '순해수욕장', '123-4567')
INSERT INTO FAVORITE_FOODS(MEMBER_ID, FOOD_NAME) VALUES(1,"짬뽕");
INSERT INTO FAVORITE_FOODS(MEMBER_ID, FOOD_NAME) VALUES(1,"짜장");
INSERT INTO FAVORITE_FOODS(MEMBER_ID, FOOD_NAME) VALUES(1,"탕수육");
INSERT INTO ADDRESS(MEMBER_ID, CITY, STREET, ZIPCODE) VALUES(1, '서울', '강남', '123-1234')
INSERT INTO ADDRESS(MEMBER_ID, CITY, STREET, ZIPCODE) VALUES(1, '서울', '강북', '321-4321')
//값 타입 컬렉션은 영속성 전이(Cascade) + 고아 객체 제거 기능을 필수로 가진다.
-
값 타입 컬렉션 조회 시 페치전략 (Default LAZY)
-
값 타입 컬렉션의 제약사항
- 값 타입 컬렉션에 변경 사항이 발생하면, 값 타입 컬렉션이 매핑된 테이블의 연관된 모든 데이터를 삭제하고, 현재 값 타입 컬렉션 객체에 있는 모든 값을 데이터베이스에 다시 저장 한다. (연관데이터 삭제 후 저장)
- 실무에서 값 타입 컬렉션이 매핑된 테이블에 데이터가 많을 시 값 타입 컬렉션 대신 "일대다 관계" 를 고려 해야 한다. + (영속정 전이 + 고아 객체 제거)
- 값 타입 컬렉션을 사용할 때는 모두 삭제하고 다시 저장 하는 최악의 시나리오도 고려하면서 사용 해야 함
9.6 정리
-
엔티티 타입
- 식별자(@Id) 존재
- 생명 주기 존재
- em.persist(entity) 로 영속화 한다.
- em.remove(entity) 로 제거 한다.
- 공유 할 수 있다.
- 공유 참조
-
값 타입
- 식별자 없음
- 생명 주기를 엔티티에 의존함
- 공유하지 않는 것이 안전함!
- 불변 객체로 만들어 사용
-
값 타입은 정말 값 타입이라고 판단될 때만 사용 해야 한다! 위에 나열한 예제들 처럼 판단 되는 경우에만 사용 하기..!
-
특히 엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안됨..! 혼동 주의..!
Author And Source
이 문제에 관하여(JPA 스터디 (9장)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@soonworld/JPA-스터디-9장저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)