엔티티 타입
- @Entity로 정의하는 객체
- 데이터가 변해도 식별자로 지속해서 추적 가능
- 예) 회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식 가능
값 타입
- int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
- 식별자가 없고 값만 있으므로 변경시 추적 불가
- 예) 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체
- 기본값 타입
- 자바 기본 타입(int, double)
- 래퍼 클래스(Integer, Long)
- String
- 임베디드 타입(embedded type, 복합 값 타입)
- 컬렉션 값 타입(collection value type)
- 예): String name, int age
- 생명주기를 엔티티의 의존
- 예) 회원을 삭제하면 이름, 나이 필드도 함께 삭제
- 값 타입은 공유하면X
- 예) 회원 이름 변경시 다른 회원의 이름도 함께 변경되면 안됨
- int, double 같은 기본 타입(primitive type)은 절대 공유X
- 기본 타입은 항상 값을 복사함
- Integer같은 래퍼 클래스나 String 같은 특수한 클래스는 공유 가능한 객체이지만 변경X
- 새로운 값 타입을 직접 정의할 수 있음
- JPA는 임베디드 타입(embedded type)이라 함
- 주로 기본 값 타입을 모아서 만들어서 복합 값 타입이라고도 함
- int, String과 같은 값 타입

예시로 위 상황을 코드로 짜보면
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name="MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
//기간
@Embedded
private Period workPeriod;
//주소
@Embedded
private Address homeAddress;
}
@Embeddable
public class Period{
private LocalDateTime startDate;
private LocalDateTime endDate;
}
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
}
- @Embeddable: 값 타입을 정의하는 곳에 표시
- @Embedded: 값 타입을 사용하는 곳에 표시
- 기본 생성자 필수
이를 실행시켜보면

Member table에 Period, Address 변수가 모두 들어가 있는 것을 확인할 수 있다.
- 재사용
- 높은 응집도
- Period.isWork()처럼 해당 값 타입만 사용하는 의미 있는 메소드를 만들 수 있음
- 임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티티에 생명주기를 의존함
- 임베디드 타입은 엔티티의 값일 뿐이다.
- 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다.
- 객체와 테이블을 아주 세밀하게(find-grained) 매핑하는 것이 가능
- 잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많음
- 한 엔티티에서 같은 값 타입을 사용하면 컬럼 명이 중복된다.
- @AttributeOverrides, @AttributeOverride를 사용해서 컬러 명 속성을 재정의할 수 있다. (자주 사용하는 기능은 아니다.)
- 임베디드 타입의 값이 null이면 매핑한 컬럼 값은 모두 null
값 타입은 복잡한 객체 세상을 조금이라도 단순화하려고 만든 개념이다. 따라서 값 타입은 단순하고 안전하게 다룰 수 있어야 한다.
- 임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험하다.
- 부작용(side effect)이 발생한다.
예시 코드)
try{
Addressaddress=newAddress("city","street","10000");
Membermember1=newMember();
member1.setName("member1");
member1.setHomeAddress(address);
em.persist(member1);
Membermember2=newMember();
member2.setName("member2");
member2.setHomeAddress(address);
em.persist(member2);
member1.getHomeAddress().setCity("newCity");
//첫번째 멤버의 주소만 newCity로 변경하고싶어!!
tx.commit();//필수.안하면 반영이 안된다.
}catch(Exceptione){
tx.rollback();
}finally{
em.close();
}
emf.close();
}

위 상황이다.
member1과 member2가 같은 Address(주소)를 공유한다고 가정했을 때, 만약 member1이 newCity로 주소를 변경하고 싶어서 저렇게 코드를 짰다면?

업데이트 쿼리가 2번 나감과 동시에...

H2 데이터베이스에도 똑같이 member1,member2 모두 newCity로 적용되는 상황이 생긴다...
그래서 위 상황을 막기 위해서 대신 값(인스턴스)를 복사해서 사용한다.

Address copyAddress = new Address(address.getCity(), address.getStreet(), address.getZipcode());
Member member2=new Member();
member2.setName("member2");
member2.setHomeAddress(copyAddress);
em.persist(member2);
코드 중간에 복사해서 넣어준다.

그러면 해결된다!
객체 타입의 한계
- 항상 값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용을 피할 수 있다.
- 문제는 임베디드 타입처럼 직접 정의한 값 타입은 자바의 기본 타입이 아니라 객체 타입이다.
- 자바 기본 타입에 값을 대입하면 값을 복사한다.
- 객체 타입은 참조 값을 직접 대입하는 것을 막을 방법이 없다.
- 객체의 공유 참조는 피할 수 없다.
- 객체 타입을 수정할 수 없게 만들면 부작용을 원천 차단
- 값 타입은 불변 객체(immutable object)로 설계해야함
- 불변 객체: 생성 시점 이후 절대 값을 변경할 수 없는 객체
- 생성자로만 값을 설정하고 수정자(Setter)를 만들지 않으면 됨
- 참고: Integer, String은 자바가 제공하는 대표적인 불변 객체
불변이라는 작은 제약으로 부작용이라는 큰 재앙을 막을 수 있다.
- 값 타입을 하나 이상 저장할 때 사용
- @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<>();
Membermember=new Member();
member.setName("member1");
member.setHomeAddress(newAddress("homeCity","street","10000"));
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");
member.getAddressHistory().add(newAddress("old1","street","10000"));
member.getAddressHistory().add(newAddress("old2","street","10000"));
em.persist(member);
em.flush();
em.clear();

저장이 잘 되는 것을 확인할 수 있다.
- 값 타입 컬렉션도 지연 로딩 전략 사용
- @ElementCollection 의 기본 fetch는 LAZY이다.
System.out.println("=======================");
Member findMember=em.find(Member.class,member.getId());

//homeCity->newCityfindMember.getHomeAddress().setCity("newCity") <- 문제 생길 수 있음.
//수정
Addressaddress=findMember.getHomeAddress();
findMember.setHomeAddress(newAddress("newCity",address.getStreet(),address.getZipcode()));
//치킨->한식 수정 방식 (스트링은 업데이트라는 것이 X 고로 새로 만들어야함.)
findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");
System.out.println("update===============");
findMember.getAddressHistory().remove(newAddress("old1","street","10000"));
findMember.getAddressHistory().add(newAddress("newCity1","street","10000"));
- 방법 1 새로 만들어서 수정할 부분만 수정하기.
- 방법 2 제거하고 추가하는 방식으로 수정하기.
스트링같은 경우, 새로 만들어서 수정하는 방식이 불가능하므로 remove를 통해 삭제 후 add로 추가해주는 방식을 사용해야 한다.

수정이 잘 되는 것을 확인할 수 있다.
- 값 타입은 엔티티와 다르게 식별자 개념이 없다.
- 값은 변경하면 추적이 어렵다.
- 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
- 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야 함: null 입력X, 중복 저장X
- 실무에서는 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려
- 일대다 관계를 위한 엔티티를 만들고, 여기에서 값 타입을 사용
- 영속성 전이(Cascade) + 고아 객체 제거를 사용해서 값 타입 컬렉션 처럼 사용
- EX) AddressEntity
- 엔티티 타입의 특징
- 식별자O
- 생명 주기 관리
- 공유
- 값 타입의 특징
- 식별자X
- 생명 주기를 엔티티에 의존
- 공유하지 않는 것이 안전(복사해서 사용)
- 불변 객체로 만드는 것이 안전
값 타입은 정말 값 타입이라 판단될 때만 사용한다.
식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면 그것
은 값 타입이 아닌 엔티티이다.