2021. 2. 14. 16:26ㆍ프로그래밍 언어/Spring Framework
[인프런 김영한] JPA - 값 타입과 불변 객체
해당 글은 인프런 김영한강사님의 영상을 보고 정리한 글입니다.
Spring Boot, Spring Data JPA를 사용해 실습하였습니다.
김영한 인프런 : www.inflearn.com/users/@yh
▣ 값타입 컬렉션
* 값타입을 컬렉션에 담아서 사용하는 방법
* 값타입을 하나 이상 저장할 떄 사용.
- @ElementCollection, @CollectionTable 사용한다.
* 데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.
* 컬렉션을 저장하기 위한 별도의 테이블이 필요함.
▣ 실습 예제
◈ 등록
Member
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@Embedded
private Address homeAddress;
@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<>();
}
Addrress
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
// 기본 생성자 필수
public Address() {
}
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
}
실행
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("homeCity","street","10000"));
//Collection
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"));
memberRepository.save(member);
// 실행결과
Hibernate:
insert
into
member
(city, street, zipcode, username, member_id)
values
(?, ?, ?, ?, ?)
Hibernate:
insert
into
address
(member_id, city, street, zipcode)
values
(?, ?, ?, ?)
Hibernate:
insert
into
address
(member_id, city, street, zipcode)
values
(?, ?, ?, ?)
Hibernate:
insert
into
favorite_food
(member_id, food_name)
values
(?, ?)
Hibernate:
insert
into
favorite_food
(member_id, food_name)
values
(?, ?)
Hibernate:
insert
into
favorite_food
(member_id, food_name)
values
(?, ?)
| 값타입 컬렉션을 따로 save하지 않았는데 다른 테이블인데도 불구하고 함께 저장이 되었다는것을 볼 수 있습니다.
왜냐하면 값타입이기 때문에 본인에 대한 LifeCycle이 없으며 Member에게 종속되어 있습니다.
크게보면 username도 값타입, FAVORITE_FOOD도 값타입이라고 볼 수 있습니다.
◈ 조회
실행
memberRepository.save(member);
memberRepository.flush();
System.out.println("===========================");
Member member1 = memberRepository.findById(member.getId()).get();
결과
===========================
Hibernate:
select
member0_.member_id as member_i1_2_0_,
member0_.city as city2_2_0_,
member0_.street as street3_2_0_,
member0_.zipcode as zipcode4_2_0_,
member0_.username as username5_2_0_
from
member member0_
where
member0_.member_id=?
| 쿼리를 확인해 보면 member 컬럼만 가지고 오게 되며 이말은 LAZY 전략을 기본적으로 사용합니다.
Embedded로 설정된 Address는 member에 소속된 값타입이기 때문에 함께 조회됩니다.
@ElementCollection는 LAZY전략으로 기본 Default되어 있습니다.
◈ 수정
//homeCity -> newCity
member1.getHomeAddress().setCity("newCity");
| 위 처럼 값타입을 set으로 변경하면 update문은 잘 실행되나 사이트 이펙트가 발생할 요소이기 때문에 이럴때는
아래처럼 Address를 새로운 값타입으로 넣어준다.
//homeCity -> newCity
// member1.getHomeAddress().setCity("newCity");
Address homeAddress = findMember.getHomeAddress();
findMember.setHomeAddress(new Address("newCity",homeAddress.getStreet(), homeAddress.getZipcode()));
* Collection을 다룰 때 equals를 오버라이드 하는게 의미가 있다.
▣ 값타입 걸렉션의 제약사항
* 값타입은 Entity와 다르게 식별자 개념이 없다.
* 값은 변경하면 추적이 어렵다.
* 값타입 컬렉션에 변경 사항이 발생하면, 주인 Entity와 연관된 모든 데이터를 삭제하고, 값타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
* 값타입 걸렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야함. Not Null, 중복 저장 X
> 복잡하다면 이런 부분은 다르게 풀어야 합니다.
▣ 값 타입 컬렉션 대안
* 실무에서는 상황에 따라 값타입 컬렉션 ㄷ대신에 일대다 고나계를 고려
* 일대다 관계를 위한 Entity를 만들고, 여기에서 값 타입을 사용
* 영속성전이(CASCADE) + 고아 객체 제거를 사용해서 값타입 컬렉션 처럼 사용
AddressEntiity
@Entity
@Table(name = "ADDRESS")
public class AddressEntity {
@Id
@GeneratedValue
private Long id;
private Address address;
}
Member 수정
// @ElementCollection
// @CollectionTable(name = "ADDRESS", joinColumns =
// @JoinColumn(name = "MEMBER_ID")
// )
// private List<Address> addressHistory = new ArrayList<>();
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "MEMBER_ID")
private List<AddressEntity> addressHistory = new ArrayList<>();
▣ 값타입 언제 쓸까요?
-> 진짜 단순할 때
- 치킨, 피자를 select box로 만들어서 사용할 떄
▣ 정리
* Entity 타입의 특징
- 식별자 O(ID)
- 생명주기 관리
- 공유
* 값타입의 특징
- 식별자 X (ID)
- 생명주기를 Entity에 의존
- 공유하지 않는 것이 안전 (복사해서 사용)
- 불변 객체로 만드는 것이 안전
- Entity와 값타입을 혼동해서 Entity를 값타입으로 만들면 X
- 식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면 그건 값타입이 아닌 Entity
'프로그래밍 언어 > Spring Framework' 카테고리의 다른 글
[인프런 김영한] JPA - JPQL 기본문법 (0) | 2021.02.15 |
---|---|
[인프런 김영한] JPA - 객체지향 쿼리 언어(JPQL, CRITERIA, QueryDSL, 네이티브 SQL, JDBC) (0) | 2021.02.14 |
[인프런 김영한] JPA - 값 타입과 불변 객체 (0) | 2021.02.12 |
[인프런 김영한] JPA - 임베디드 타입(복합 값 타입) (0) | 2021.02.12 |
[인프런 김영한] JPA - 영속성전이(CASCADE) (0) | 2021.02.11 |