값 타입은 복잡한 객체 세상을 단순화하려고 만든 개념이다.
따라서 값 타입은 단순하고 안전하게 다룰 수 있어야 한다.
임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험하다.
member1.setHomeAddress(new Address("OldCity"));
Address address = member1.getHomeAddress();
address.setCity("NewCity"); // 회원1의 address 값을 공유해서 사용
member2.setHomeAddress(address);
"회원2의 주소만 "NewCity"로 변경되길 기대했지만 회원1의 주소도 "NewCity"로 변경"
-> 회원1과 회원2가 같은 address 인스턴스를 참조
-> 영속성 컨텍스트는 회원1과 회원2 둘 다 city 속성이 변경된 것으로 판단
-> 회원1, 회원2 각각 UPDATE SQL 실행
이러한 "부작용"을 막기 위해서는 값을 복사해서 사용!
값(인스턴스)을 복사해서 사용해야 한다.
member1.setHomeAddress(new Address("OldCity"));
Address address = member1.getHomeAddress();
// 회원1의 address 값을 복사해 새로운 newAddress 값을 생성
Address newAddress = address.clone();
newAddress.setCity("NewCity");
member2.setHomeAddress(newAddress);
"회원2의 주소만 "NewCity"로 변경"
-> clone() 메소드는 자신을 복사해서 반환하도록 구현해 회원2에 새로운 주소 할당
-> 영속성 컨텍스트는 회원2의 주소만 변경된 것으로 판단
-> 회원2만 UPDATE SQL 실행
공유 참조로 인해 발생하는 부작용 피할 수 있음!
but, 임베디드 타입처럼 직접 정의한 값 타입은 자바의 기본 타입이 아닌 객체 타입!
int a = 10;
int b = a; // 기본 타입은 항상 값을 복사
b = 4;
"a = 10, b = 4"
-> int b = a에서 a의 값 10을 복사해 b에 넘겨서 a, b는 완전히 독립된 값 & 부작용 X
Address a = new Address("Old");
Address b = a; // 객체 타입은 항상 참조 값을 전달
b.setCity("New");
"a.city = "New", b.city = "New"
-> Address b = a에서 a가 참조하는 인스턴스의 참조 값을 b에 넘겨서 a와 b는 같은 인스턴스 공유참조 & 부작용 O
객체를 대입할 때마다 인스턴스를 복사해 대입하면 공유 참조 피할 수 있지만,
복사하지 않고 원본의 참조 값을 직접 넘기는 것을 막을 방법은 없음"
Address a = new Address("Old");
Address b = a.clone(); // 항상 복사해서 넘겨야 한다.
// Address b = a; // 부작용 발생!!
b.setCity("New");
객체의 공유 참조는 피할 수 없으므로, 근본적인 해결책 필요
-> 객체의 값을 수정하지 못하게 막자!
객체를 불변하게 만들면 값을 수정할 수 없으므로 부작용을 원천 차단 가능.
따라서 값 타입은 되도록 불변 객체(immutable Oject)로 설계하자!
// 주소 불변 객체
@Embeddable
public class Address {
private String city;
protected Address() {} // JPA 에서 기본 생성자는 필수!
// 생성자로 초기 값 설정
public Address(String city) {this.city = city;}
// 접근자(Getter)는 노출
public String getCity() {return city;}
// 수정자(Setter)는 만들지 않음
}
불변이라는 작은 제약으로 부작용이라는 큰 재앙을 막을 수 있다!
int a = 10;
int b = 10;
Address a = new Address("서울시", "종로구", "1번지);
Address b = new Address("서울시", "종로구", "1번지);
- int a의 숫자 10과 int b의 숫자 10 같다고 표현한다.
- Address a와 Address b는 같다고 표현한다.
ex) Address 값 타입
- a == b : 둘은 서로 다른 인스턴스로 결과는 "거짓"
- but, 값 타입은 인스턴스가 달라도 그 안에 값이 같으면 같은 것으로 봐야하므로
a.equals(b)의 동등성 비교를 통해 값 타입을 비교해야함.
- Address의 equals() 메소드를 재정의 해야함 -> 모든 필드의 값을 비교하도록 구현
+) 자바는 equals() 재정의하면 hashCode()도 재정의하는 것이 안전
그렇지 않으면 해시를 사용하는 컬렉션 (HashSet, HashMap) 정상 동작 X
자바 IDE에는 대부분 equals, hashCode 메소드 자동으로 생성해주는 기능 있음
출처 : 자바 ORM 표준 JPA 프로그래밍 책
[자바 ORM 표준 JPA 프로그래밍] 객체지향 쿼리 언어