Orm 표준 jpa 프로그래밍 (5) - 값 타입
- -
0. JPA의 데이터 타입
엔티티 타입
- @Entity로 정의하는 객체
- 데이터가 변해도 식별자로 지속해서 추적 가능
(식별자: DB에서 인스턴스의 집합인 엔티티를 구분할 수 있는 논리적인 값,
하나의 엔티티에 구성되어 있는 여러가지 속성중에 대표할 수 있는 속성)
ex) 회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식 가능
값 타입
- int, integerm String 처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
- 식별자가 없고 값만 있으므로 변경시 추적 불가
ex) 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체
값 타입 분류
- 기본값 타입: 자바 기본 타입(int, double), 래퍼 클래스(Integer, Long), String
- 임베디드 타입
- 컬렉션 값 타입
1. 기본값 타입
- ex) String name, int age
- 생명주기를 엔티티에 의존 (회원을 삭제하면 이름, 나이 필드도 함께 삭제)
- 값 타입은 공유 x (회원 이름 변경시 다른 회원의 이름도 함께 변경되면 안됨)
- 자바의 기본 타입은 절대 공유되면 안됨
int, double 같은 기본 타입은 절대 공유x
기본 타입은 항상 값을 복사
Integer같은 래퍼 클래스나 String같은 특수한 클래스는 공유 가능한 객체이지만 공유x
2. 임베디드 타입
- 새로운 값 타입을 직접 정의할 수 있음
- JPA는 이것을 임베디드 타입이라고 함
- 주로 기본 값 타입을 모아서 만들어서 복합 값 타입이라고도 함
- int, String과 같은 값 타입
- @Embeddable: 값 타입을 정의하는 곳에 표시
- @Embedded: 값 타입을 사용하는 곳에 표시
- 기본 생성자 필수 !!
- 장점: 1. 재사용 2. 높은 응집도 3. 해당 값 타입만 사용하는 의미 있는 메소드 만들 수 있음 4. 임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티티에 생명주기를 의존함
- 임베디드 타입은 엔티티의 값일 뿐
- 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같음
- 객체와 테이블을 아주 세밀하게 매핑하는 것이 가능
- 잘 설계한 ORM 어플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많음
- 한 엔티티에서 같은 값 타입을 사용하면 컬럼 명이 중복되기 때문에 @AttributeOverrides, @AttributeOverride를 사용해서 컬럼 명 속성을 재정의
- 임베디드 타입의 값이 null이면 매핑한 컬럼 값은 모두 null
3. 값 타입과 불변 객체
- 값 타입은 단순하고 안전하게 다룰 수 있어야 함
값 타입 공유 참조
- 임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험함 > 부작용(side-effect)발생
값 타입 복사
- 값 타입의 실제 인스턴스인 값을 공유하는 것은 위험 > 값(인스턴스)을 복사해서 사용
객체 타입의 한계
- 값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용을 피할 수 있음
- 임베디드 타입처럼 직접 정의한 값 타입은 자바의 기본 타입이 아니라 객체 타입
- 자바 기본 타입에 값을 대입하면 값을 복사
- 객체 타입은 참조 값을 직접 대입하는 것을 막을 방법이 없음
- 객체의 공유 참조는 피할 수 없음
// 기본 타입
int a = 10;
intb=a;//기본 타입은 값을 복사
b = 4;
// 객체 타입
Address a = new Address(“Old”);
Address b = a; //객체 타입은 참조를 전달
b. setCity(“New”)
- 하지만 객체 타입을 수정할 수 없게 만들면 부작용이 차단됨
- 값 타입은 불변 객체(생성 시점 이후 절대 값을 변경할 수 없는 객체)로 설계해야 함
findMember.getHomeAddress().setCity("newCity"); // 잘못된 방법
// 아래와 같이
Address a = findMember.getHomeAddress();
findMember.setHomeAddress(new Address("newCity", a.getStreet(), a.getZipcode()));
// 인스턴스 자체를 교체해야 함
- 생성자로만 값을 설정하고 수정자(setter)를 만들지 않으면 가능
ex) Integer / String 은 자바가 제공하는 대표적인 불변 객체
값 타입 비교
- 값 타입은 인스턴스가 달라도 그 안에 값이 같으면 같은 것으로 봄
int a = 10;
int b = 10;
Address a = new Address(“서울시”)
Address b = new Address(“서울시”)
- 동일성(identity) 비교: 인스턴스의 참조 값을 비교, == 사용
- 동등성(equivalence) 비교: 인스턴스의 값을 비교, equals() 사용
- 값 타입은 a.equals(b)를 사용해서 동등성 비교를 해야 함!
- 값 타입의 equals() 메소드를 적절하게 재정의(주로 모든 필드 사용)
값 타입 컬렉션
- 값 타입을 하나 이상 저장할 때 사용
- @ElementCollection / @CollectionTable 사용
@ElementCollection
@CollectionTable(name = "FAVORITE_FOOD", 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<>();
- DB는 컬렉션을 같은 테이블에 저장할 수 없음
- 컬렉션을 저장하기 위한 별도의 테이블이 필요
try{
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address(city1, street, 10000));
member.getFavoriteFoods().add("치킨")
member.getFavoriteFoods().add("족발")
member.getFavoriteFoods().add("피자")
member.getAddressHistory().add(new Address(old1, street, 10000))
member.getAddressHistory().add(new Address(old2, street, 10000))
em.persist(member);
em.flush()
em.clear() // DB에 데이터를 보내고 깨끗한 상태에서 진행
tx.commit();
}
// member 엔티티만 persist 했는데 다른 테이블 값 타입 컬렉션인 FAVORITE_FOOD 등도 같이 저장이 됨
// 값 타입 컬렉션은 생명주기가 member(엔티티)가 의존됨
- 값 타입 컬렉션은 영속성 전이와 고아 객체 제거 기능을 필수로 가진다고 볼 수 있음
// String은 그 자체가 값 타입
// 변경이 안되고 그 자체를 교체해야 함
findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");
// equals(Object o) 기반이기에 이 메서드가 제대로 설정되어 있어야 함
findMember.getAddressHistory().remove(new Address(old1, street, 10000));
findMember.getAddressHistory().add(new Address(newCity1, street, 10000));
// old1이 삭제되고 newCity1이 들어가는것이 아닌
// old1을 포함한 전체가 삭제되고 old1 대신 newCity1을 포함한 모든 데이터가 재입력됨
값 타입 컬렉션의 제약사항
- 값 타입은 엔티티와 다르게 식별자 개념이 없음
- 값은 변경하면 추적이 어려움
- 값 타입 컬렉션 변경됨 => 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장
- 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본키를 구성해야 함 (null과 중복 입력 허용X)
- 실무에서는 상황에 따라 값 타입 컬렉션보다 일대다 관계를 고려
- 값 타입 컬렉션 이용하면 @ElementCollection / @CollectionTable 을 사용해야 됨
- 일대다 관계는 엔티티 클래스를 생성하고 교체나 삭제를 위해 equals() / hashcode() 가 구현되어야 함(equals()는 프록시 객체로 인해 getter가 구현되어야 함, 프록시로 바로 접근을 시도하면 꼬이게 됨)
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name="MEMBER_ID")
private List<AddressEntity> addressHistory = new ArrayList<>();
---------------------------------
@Table(name = "ADDRESS")
@Entity
public class AddressEntity{
@Id @GeneratedValue
private Long id;
// 기본 생성자
public AddressEntity(String city, String streer, String zipcode){}
public AddressEntity(Address address){
this.address = address
}
...
// getter & setter
}
----------------------------------
// member.getAddressHistory().add(new Address(old1, street, 10000)); 를 아래처럼
member.getAddressHistory().add(new AddressEntity(old1, street, 10000));
엔티티 타입의 특징
- 식별자 O
- 생명 주기 관리
- 공유
값 타입의 특징
- 식별자 X
- 생명 주기를 엔티티에 의존
- 공유하지 않는 것이 안전(복사해서 사용)
- 불변 객체로 만드는 것이 안전
- 추적할 필요도 없고 값이 바껴도 업데이트 할 필요가 없을 때(ex: 좋아하는 음식 체크) 값 타입 컬렉션 사용하고 그 외(ex: 주소 이력)에는 엔티티 사용
'back > spring' 카테고리의 다른 글
JPA - 엔티티 기본 생성자 (0) | 2023.05.15 |
---|---|
Orm 표준 jpa 프로그래밍 (6) - 객체 지향 쿼리 언어 (0) | 2023.05.10 |
Orm 표준 jpa 프로그래밍 (4) - 매핑, 프록시 (0) | 2023.05.08 |
Orm 표준 jpa 프로그래밍 (3) - 연관관계 매핑 (0) | 2023.05.08 |
Orm 표준 jpa 프로그래밍 (2) - 엔티티 매핑 (0) | 2023.05.08 |
소중한 공감 감사합니다