값 타입과 불변 객체
값 타입 공유 참조
값 타입을 여러 엔티티에 공유하면 위험하다
예로 들어, 회원1,2가 같은 값 타입을 공유할 때, 회원1의 값을 바꾸면 회원2의 값도 함께 바뀐다
이런 현상을 부작용이라 한다
값 타입 복사
값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용을 피할 수 있다
자바는 기본 타입에 값을 대입(=)하면 값을 복사해서 전달한다
자바는 객체에 값을 대입하면 항상 참조 값을 전달한다
복사하지 않고 원본의 참조 값을 직접 넘기는 것을 막을 방법이 없다
불변 객체
객체를 불변하게 만들면 값을 수정할 수 없으므로 부작용을 차단할 수 있으므로 값 타입은 가능하다면 불변 객체로 설계해야 한다
불변 객체는 수정자를 만들지 않는 형태로 구현되며, 값을 수정해야 할 경우 새로운 객체를 생성해 사용해야 한다
값 타입의 비교
동일성 비교: 인스턴스의 참조 값을 비교
동등성 비교: 인스턴스의 값을 비교
인스턴스가 달라도 값이 같으면 같은 것으로 봐야 한다
equals() 메소드를 재정의해 동등성 비교를 사용해야 한다
값 타입 컬렉션
값 타입을 하나 이상 저장하려면 컬렉션에 보관하고 @ElementCollection, @CollectionTable을 사용하면 된다
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Embedded
private Address homeAddress;
@ElementCollection
@CollectionTable(name="FAVORIATE_FOODS",
joinColumns=@JoinColumn(name="MEMBER_ID")
@Column(name="FOOD_NAME")
private Set<String> favoriteFoods = new HashSet<String>();
@ElementCollection
@CollectionTable(name="ADDRESS",
joinColumns=@JoinColumn(name="MEMBER_ID")
private List<Address> addressHistory = new ArrayList<Address>();
...
}
@Embeddable
public class Address {
@Column
private String city;
private String street;
private String zipcode;
...
}
데이터베이스 테이블은 컬럼 안에 컬렉션을 포함할 수 없다
값 타입 컬렉션을 저장한 별도의 테이블을 추가하고 추가한 테이블을 매핑해야 한다(@CollectionTable)
추가한 테이블의 값으로 사용되는 컬럼이 하나 뿐이라면 @Column을 통해 컬럼명을 지정할 수 있다
@CollectoinTable을 생략하면 기본 값을 사용해서 매핑한다 (엔티티이름_컬렉션 속성이름)
값 타입 컬렉션 사용
Member member = new Member();
member.setHomeAddress(new Address("통영", "몽돌해수욕장", "660-123");
member.getFavoriteFoods().add("짬뽕");
member.getFavoriteFoods().add("짜장면");
member.getFavoriteFoods().add("탕수육");
member.getAddressHistory().add(new Address("서울", "강남", "123-123"));
member.getAddressHistory().add(new Address("서울", "강북", "000-000"));
em.persist(member);
마지막에 엔티티를 영속화하면, 해당 엔티티의 값 타입도 함께 저장한다
영속화 과정에서 INSERT문은 총 6번 실행된다
- INSERT문: member 1번, favorite_food 3번, address_history 2번 → 총 6번
값 타입 컬렉션 조회 시 페치 전략을 선택할 수 있는데, 기본은 LAZY이다
- @ElementCollection(fetch=FetchType.LAZY)
Member member = em.find(Member.class, 1L);
member.setHomeAddress(new Address("새로운도시", "신도시1", "123456");
Set<String> favoriteFoods = member.getFavoriteFoods();
favoriteFoods.remove("탕수육");
favoriteFoods.add("치킨")
List<Address> addressHistory = member.getAddressHistory();
addressHistory.remove(new Address("서울", "기존 주소", "123-123");
addressHistory.add(new Address("새로운도시", "새로운 주소", "123-456");
임베디드 값 타입은 수정할 새로운 객체를 생성해 해당 값을 수정하면 된다
값 타입 컬렉션의 경우 기존 값 타입을 수정하는 것이 아니라 값 타입을 삭제하고 새로운 값 타입을 추가해야 한다
- 값 타입은 equals, hashcode를 꼭 구현해야 한다
값 타입 컬렉션의 제약사항
1. 값 타입은 식별자가 없는 단순한 값의 모음이므로 값을 변경하면 데이터베이스에 저장된 원본 데이터를 찾기 어렵다
-> 값 타입은 변경되어도 자신이 소속된 엔티티를 찾아 변경하면 되지만, 값 타입 컬렉션은 값 타입들이 별도의 테이블에 저장되는데 여기에 보관된 값 타입이 변경되면 원본 데이터를 찾기 어렵다
2. 값 타입 컬렉션에 변경 사항이 발생하면 값 타입 컬렉션의 모든 데이터를 삭제하고 모든 데이터를 다시 저장한다
-> 값 타입 컬렉션이 매핑된 테이블의 데이터가 많다면 값 타입 컬렉션 대신 일대다 관계를 고려해야 한다
3. 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본키를 구성한다
-> 기본 키 제약 조건으로 인해 컬럼에 null을 입력할 수 없다
-> 같은 값을 중복해서 저장할 수 없다
일대다 관계 + 영속성 전이 + 고아 객체 제거 기능을 사용하면 값 타입 컬렉션처럼 사용할 수 있다
'스터디 > JPA' 카테고리의 다른 글
객체지향 쿼리 언어 - (2) (0) | 2025.02.17 |
---|---|
객체지향 쿼리 언어 - (1) (0) | 2025.02.16 |
값 타입 - (1) (0) | 2025.02.14 |
프록시와 연관관계 정리 - (3) (1) | 2025.02.14 |
프록시와 연관관계 정리 - (2) (0) | 2025.02.13 |