값 타입은 복잡한 객체 세상을 조금이라도 단순화하려고 만든 개념이다.
따라서 값 타입은 단순하고 안전하게 다룰 수 있어야 한다.
Address address = new Address("city", "street", "10000");
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(address);
em.persist(member);
member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(address);
em.persist(member2);
// member1이 아닌 member 객체의 city만 바꾸고 싶어!
member.getHomeAddress().setCity("newCity");
// 그러나 member와 member2의 city가 모두 newCity로 바뀐다.
// 이런 버그는 찾아내기가 힘들다.
// 지금이야 바로 밑에 코드를 작성했지만 실제로는 한참떨어진 곳에 작성하므로
위의 코드를 보면 알 수 있듯이 임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험하다. 위와 같은 예상치 못한 사이드 이펙트(부작용)가 발생할 수 있고, 디버깅하기도 힘들기 때문이다.
Address address = new Address("city", "street", "10000");
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(address);
em.persist(member);
// 깊은 복사
Address copyAddress = new Address(address.getCity(),
address.getStreet(),
address.getZipcode());
Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(copyAddress);
em.persist(member2);
// 이렇게 하면 member의 city만 변하게 된다.
member.getHomdAddress().setCity("newCity");
값 타입의 공유참조는 매우 위험하다.
대신 깊은 복사(참조값을 복사하는 것이 아닌 값을 복사하는 것)를 해서 사용하면 부작용을 없앨 수 있다.
int a = 10;
int b = a;
b = 4;
System.out.println(a); // 10
System.out.println(b); // 4
Address a = new Address("old");
Address b = a; // 객체 타입은 참조값을 복사한다.
b.setCity("new");
System.out.println(a.getCity()); // new
System.out.println(b.getCity()); // new
[Address]
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
}
[JpaMain]
Address address = new Address("city", "street", "10000");
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(address);
em.persist(member);
Address newAddress = new Address("newCity",
address.getStreet(),
address.getZipCode());
member.setHomeAddress(newAddress);
불변이라는 작은 제약으로 부작용이라는 큰 재앙을 막을 수 있다.