Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 예제 도메인 모델
- 기본문법
- 임베디드 타입
- 불변 객체
- 컬렉션 조회 최적화
- 김영한
- 스프링
- 벌크 연산
- 프로젝트 환경설정
- 검증 애노테이션
- 타임리프
- 스프링 mvc
- JPA
- 스프링 데이터 JPA
- Spring Data JPA
- 실무활용
- 스프링MVC
- jpa 활용
- API 개발 고급
- 타임리프 문법
- QueryDSL
- JPA 활용 2
- 페이징
- JPQL
- JPA 활용2
- 트위터
- 로그인
- 일론머스크
- Bean Validation
- 값 타입 컬렉션
Archives
- Today
- Total
RE-Heat 개발자 일지
[JPA] [9] 값 타입 (하편) 본문
https://www.inflearn.com/course/ORM-JPA-Basic
인프런 김영한 님의 강의를 듣고 작성한 글입니다.
[4] 값 타입의 비교
값 타입은 인스턴스가 달라도 값이 같으면 같은 것으로 봐야 한다.
//값 타입 비교
int a = 10;
int b = 10;
System.out.println("a == b: " + (a == b)); // true
//객체 타입 비교
Address addr1 = new Address(“서울시”)
Address addr2 = new Address(“서울시”)
System.out.println("addr1 == addr2: " + (addr2 == addr2)); // false
- 값 타입은 인스턴스가 달라도 값이 같으면 같은 것으로 본다.
- 반면 객체 타입은 값이 같아도 주소값이 달라 == 비교하면 false로 반환한다.
■ 동일성 vs 동등성
- 동일성(identity) 비교: 인스턴스의 참조 값을 비교, == 사용 [primitive 타입]
- 동등성(equivalence) 비교: 인스턴스의 값을 비교, equals() 사용 [임베디드 타입]
- 값 타입은 a.equals(b)를 사용해 동등성 비교를 해야 한다.
- 값 타입의 equals() 메소드를 적절하게 재정의해야 한다. 추가로 hashcode()도 지정해야 한다.
- 롬복을 쓰면 @EqualsAndHashCode 애노테이션을 붙여주면 된다.
- 아니면 인텔리J에서 자동으로 만들어주는 equals()를 쓰면 된다.
참고] hashCode() : 두 객체가 같은 객체인지 동일성을 비교하는 연산자
결론 : 직접 정의한 값 타입은 Equals()를 만들고 동등성 비교를 해야 한다.
[5] 값 타입 컬렉션
값 타입을 하나 이상 저장할 때 사용한다.
• 데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.
• 컬렉션은 개념적으로 일대다이기 때문에 저장하기 위한 별도의 테이블이 필요함
▶ 예제
MemberEmb
@Entity
@Getter
@Setter
@NoArgsConstructor
public class MemberEmb {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@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<>();
}
- @ElementCollection으로 값 타입 컬렉션이라는 것을 명시한다.
- @ColletionTable로 컬렉션 테이블 이름을 지정하고, @JoinColumn으로 Member와의 외래키를 지정한다.
- FavoriteFoods에만 @Columb이 들어간 이유는?
- Address엔 필드 이름이 정해져 있지만, favoriteFoods는 제너릭에 String이 들어가 있어 필드의 이름을 지정하기 위해 @Column을 썼다.
▶ 값 타입 컬렉션
① 저장
JpaMain - 저장
MemberEmb member = new MemberEmb();
member.setUsername("member1");
member.setHomeAddress(new Address("homeCity", "street", "10000"));
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");
member.getAddressHistory().add(new AddressEntity("old1", "street", "10000"));
member.getAddressHistory().add(new AddressEntity("old2", "street", "10000"));
em.persist(member);
tx.commit();
- em.persist()로 Insert SQL이 Member 1개, FavoriteFood 3개, Address 2개 총 6개 날아간다.
- 값 타입 컬렉션 persist 해주지 않아도 member가 persist 되면 알아서 SQL이 날아간다.
- 값 타입이라 라이프 사이클이 없기 때문이다.
- 이는 일대다 연관관계에서 Cascade=ALL + orphanRemoval=true로 설정한 것과 유사하다.
- 따라서 값 타입 컬렉션은 영속성 전이(Cascade) + 고아 객체 제거 기능을 필수로 가진다고 볼 수 있다.
② 조회
- 우선 Member만 가져왔다. 이는 컬렉션 값 타입이 지연 로딩 전략을 취한다는 것을 의미한다.
- 실제로 address.getCity()나 favoriteFoods를 호출해야 그때 맞춰 select 쿼리를 날리는 것을 확인할 수 있다.
③ 수정
1. 기본 개념
//homeCity -> newCity
//틀린 방식
//findMember.getHomeAddress().setCity("newCity");
Address a = findMember.getHomeAddress();
findMember.setHomeAddress(new Address("newCity", a.getStreet(), a.getZipcode()));
- 일반적으로 컬렉션을 수정할 땐 위 방식을 쓴다.
- 객체 안 값을 변경 X, 새로운 객체를 만들어서 통째로 갈아 끼워야 한다.
2. Set<String> 수정
//치킨 => 한식
//String은 통째로 갈아끼워야 함.
findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");
- 불변객체인 String을 수정할 땐 '치킨'을 지우고 '한식'을 추가해 주는 방법 밖에 없다.
- String이 값 타입이므로 업데이트가 불가능(setter)하다. 통째로 바꿔야 한다.
//주소 바꾸기
//remove()안에 equals로 확인하기 때문에 equals hashcode가 중요하다!
findMember.getAddressHistory().remove(new Address("old1", "street", "10000"));
findMember.getAddressHistory().add(new Address("newCity1", "street", "10000"));
remove()를 할 때 객체가 맞는지 equals(), hashcode()로 확인하므로 이에 대한 재정의가 중요하다.
■ 값 타입 컬렉션의 제약사항
- 값 타입은 엔티티와 다르게 식별자 개념이 없다.
- 값은 변경하면 추적이 어렵다.
- Address는 id(식별자)가 없으므로 값이 중간에 변경되면 어떤 게 변경되었는지 알기 힘들다.
- 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본키를 구성해야 한다: null 입력 X, 중복 저장 X
- 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
- 대안 : @OrderColumn()을 사용
- @OrderColumn으로 값 타입 컬렉션으로 생성된 테이블에 식별자·기본키를 제대로 넣어주면 insert가 아닌 update 쿼리가 날아간다. 하지만, 이런 방법도 원하는 대로 동작하지 않을 때가 있어 위험하다.
- 결론 : 값 타입 컬렉션은 정말 단순할 때(추적 X, 업데이트 X)만 사용하고 가급적 사용하지 않는 게 낫다.
- 예시) 셀렉트 박스에서 [치킨, 피자] 등 여러 메뉴 선택할 때
■ 값 타입 컬렉션의 대안
- 실무에서는 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려
- 일대다 관계를 위한 엔티티를 만들고, 여기에서 값 타입을 사용(값 타입 엔티티로 승급)
- 영속성 전이(Cascade) + 고아 객체 제거를 사용해서 값 타입 컬렉션처럼 사용
AddressEntity
@Entity
@Getter
@Setter
@Table(name = "ADDRESS")
@NoArgsConstructor
@AllArgsConstructor
public class AddressEntity {
@Id
@GeneratedValue
private Long id;
private Address address;
public AddressEntity(String city, String street, String zipcode) {
this.address = new Address(city, street, zipcode);
}
}
MemberEmb
// @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<>();
- 값 타입 컬렉션 대신 일대다 매핑을 하고 CascadeType.ALL + orphanRemoval = true로 읽기만 가능하게 만듦
DB 확인
- Address에 자체적인 Id가 생긴 걸 알 수 있다.
■ 정리
- 엔티티 타입의 특징
- 식별자 O
- 생명 주기 관리
- 공유
- 값 타입의 특징
- 식별자 X
- 생명 주기를 엔티티에 의존
- 공유하지 않는 것이 안전(복사해서 사용)
- 불변 객체로 만드는 것이 안전
[6] 실전 예제 6 - 값 타입 매핑
- city, street, zipcode를 묶어 값 타입으로
- Member와 Delivery에 있던 city, street, zipcode를 Address로 대체
- @Column(length=10)로 글자 수를 제한하거나 fullAddress() 같은 메소드를 만들어 사용하면 값 타입의 장점을 극대화할 수 있다.
- equals메소드를 오버라이드 할 땐 getter로 필드에 접근해야 한다. 이렇게 하지 않으면 프록시에 접근할 땐 값을 얻어올 수 없기 때문이다.
'백엔드 > JPA' 카테고리의 다른 글
[JPA] [10] 객체지향 쿼리 언어 - 기본 문법 (하편) (0) | 2023.08.17 |
---|---|
[JPA] [10] 객체지향 쿼리 언어 - 기본 문법 (상편) (0) | 2023.08.16 |
[JPA] [9] 값 타입 (상편) (0) | 2023.08.13 |
[JPA] [8] 프록시와 연관관계 관리 (0) | 2023.08.12 |
[JPA] [7] 고급 매핑 (0) | 2023.08.11 |