(3) 값 타입과 불변 객체
값 타입은 객체 세상을 조금이라도 단순화하려고 만든 개념이다. 따라서 값 타입은 단순하고 안전하게 다룰 수 있어야 한다.
9.3.1 값 타입 공유 참조
임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험하다.
// 값 타입 공유 시 문제 상황member1.setHomeAddress(new Address("OldCity"));Address address = member1.getHomeAddress();address.setCity("NewCity"); // 회원 1의 address 값을 공유해서 사용member2.setHomeAddress(address);
=> 회원1과 회원2가 같은 address
인스턴스를 참조해서 영속성 컨텍스트는 회원1과 회원2 둘 다 city 속성이 변경된 것으로 판단해서
회원1, 회원2 각각 UPDATE SQL을 실행한다.
이러한 공유 참조로 발생하는 버그는 정말 찾아내기 어렵다. 이렇게 뭔가를 수정했는데 전혀 예상치 못한 곳에서 문제가 발생하는 것을 부작용(Side Effect) 라고 한다.
이런 부작용을 막으려면 값을 복사해서 사용하면 된다.
9.3.2 값 타입 복사
값 타입의 실제 인스턴스인 값을 공유하는 것은 위험하다. 대신에 값(인스턴스)을 복사해서 사용해야 한다.
member1.setHomeAddress(new Address("OldCity"));Address address = member1.getHomeAddress();// 회원 1의 address 값을 복사해서 새로운 newAddress 값을 생성Address newAddress = address.clone(); // clone 은 자신을 복사해서 반환하도록 구현되어 있다.newAddress.setCity("NewCity");member2.setHomeAddress(newAddress);
영속성 컨텍스트는 회원 2의 주소만 변경된 것으로 판단해서 회원 2에 대해서만 UPDATE SQL을 실행한다.
이렇게 객체를 대입할 때마다 인스턴스를 복사해서 대입하면 공유 참조를 피할 수 있다. 하지만 근본적인 객체 공유 참조를 피할 수는 없다.
따라서 부작용을 막기 위한 가장 단순한 방법은 객체의 값을 수정하지 못하게 막는 것이다. (Setter 제거)
9.3.3 불변 객체
객체를 불변하게 만들면 값을 수정할 수 없으므로 부작용을 원천 차단할 수 있다. 따라서 값 타입은 될 수 있으면 불변 객체로 설계해야 한다.
불변 객체
한 번 만들면 절대 변경할 수 없는 객체. 불변 객체의 값은 조회할 수 있지만 수정할 수 없다.
불변 객체도 결국은 객체여서 인스턴스의 참조 값 공유를 피할 수 없다.
하지만 참조 값을 공유해도 인스턴스의 값을 수정할 수 없으므로 부작용이 발생하지 않는다.
가장 간단 한 방법은 생성자로만 값을 설정하고, 수정자를 만들지 않는 것이다.
// 주소 불변 객체@Embeddablepublic class Address {private String city;protected Address() {} // JPA 에서 기본 생성자는 필수이다.// 생성자로 초기 값을 설정한다.public Address (String city) { this.city = city;}// 접근자 (Getter) 는 노출한다.public String getCity() {return city;}// 수정자 (Setter) 는 만들지 않는다.}
Address
는 이제 불변 객체다. 값을 수정할 수 없으므로 공유해도 부작용이 발생하지 않는다.
만약 값을 수정해야하면 새로운 객체를 생성해서 사용해야 한다.
// 불변 객체 사용Address address = member1.getHomeAddress();// 회원 1의 주소값을 조회해서 새로운 주소값을 생성Address newAddress = new Address(address.getCity());member2.setHomeAddress(newAddress);
불변이라는 작은 제약으로 부작용이라는 큰 재앙을 막을 수 있다.