Search
Duplicate
📒

[JPA 기본] 09-1. 값 타입 ⭐

상태
완료
수업
JPA
주제
JPA
연관 노트
3 more properties
참고

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임베디드 타입을 가지고, PhoneNumberPhoneEntity엔티티를 가질 수 있다.

@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
복사
위와같이, 새로운 memberaddress를할당하기 위해 새로운 복사객체를 하나 만든다.

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
복사
실무에서는 상황에 따른 값 타입 컬렉션 대신 일대다 관계를 고려한다.
영속성 전이 + 고아 객체 제거를 사용해서 값 타입 컬렉션 처럼 사용한다.