회원 1과 회원 2가 같은 city를 갖고 있을 때 회원 1의 city를 바꾸면 회원 2의 city도 바뀐다.
publicclassJpaMain {publicstaticvoidmain(String[] args) {// 같은 Address 값을 세팅한다.Address address =newAddress("city","street","10012");Member member =newMember();member.setUsername("member1");member.setAddress(address);em.persist(member);Member member2 =newMember();member2.setUsername("member2");member2.setAddress(address);em.persist(member2);tx.commit(); }}
일단 같은 주소를 회원 1, 2에게 넣어준다.
publicclassJpaMain {publicstaticvoidmain(String[] args) {Address address =newAddress("city","street","10012");Member member =newMember();member.setUsername("member1");member.setAddress(address);em.persist(member);Member member2 =newMember();member2.setUsername("member2");member2.setAddress(address);em.persist(member2);// 첫번째 멤버의 주소만 변경하려고 한다.member.getAddress().setCity("new City");tx.commit(); }}
그리고 나서 회원 1의 주소를 바꾼다면?
업데이트 쿼리가 두 번 나간 것을 볼 수 있다.
실제 데이터도 회원 1, 2 모두가 바뀌었다. 이런 버그는 찾기가 굉장히 어렵다.
값 타입의 실제 인스턴스 값을 공유하는 것은 매우 위험하다.
일부러 데이터를 공유해서 사용하고 싶다면 임베디드 타입이 아니라 Entity로 만들어서 공유해야 한다.
대신, 값(인스턴스)를 복사해서 사용해야 한다.
publicclassJpaMain {publicstaticvoidmain(String[] args) {Address address =newAddress("city","street","10012");Member member =newMember();member.setUsername("member1");member.setAddress(address);em.persist(member);// 값을 복사해서 사용한다.Address address2 =newAddress(address.getCity(),address.getStreet(),address.getZipcode());Member member2 =newMember();member2.setUsername("member2");member2.setAddress(address2);em.persist(member2);// 이제 회원 1만 update 쿼리가 나간다.member.getAddress().setCity("new City");tx.commit(); }}
값을 복사해서 사용해야 의도대로 데이터가 돌아간다.
객체 타입의 한계
항상 값을 복사해서 사용하면 공유 참조 때문에 발생하는 부작용을 피할 수 있다.
그런데 임베디드 타입 등 직접 정의한 타입은 자바 기본 타입이 아니라 객체 타입이다.
primitive 같은 값 타입은 =으로 할당해도 복사해서 들어가서 두 변수를 한 번에 수정하는 사이드 이펙트가 발생하지 않는다.
객체 타입은 참조 값을 직접 가지므로 둘 다 값이 바뀌는 사이드 이펙트를 막을 방법이 없다.
즉, 객체의 공유 참조는 피할 수 없다.
publicclassJpaMain {publicstaticvoidmain(String[] args) {Address address =newAddress("city","street","10012");Member member =newMember();member.setUsername("member1");member.setAddress(address);em.persist(member);Address address2 =newAddress(address.getCity(),address.getStreet(),address.getZipcode());Member member2 =newMember();member2.setUsername("member2");// 실수로 기존의 address를 삽입한다면?member2.setAddress(address);em.persist(member2);member.getAddress().setCity("new City");tx.commit(); }}
만약 실수로 복사한 값 대신 이전 값을 집어넣는다면, 컴파일 단계에서 이 문제를 짚어낼 수 없다.
기본 타입은 값을 복사하기 때문에 문제가 없지만, 객체 타입은 참조를 전달하기 때문에 변경하면 둘 다 반영되는 부작용이 발생한다.
불변 객체
생성 시점 이후 절대 값을 변경할 수 없는 객체
Integer, String은 자바가 제공하는 대표적인 불변 객체다.
생성자로만 값을 설정하고 수정자(setter)를 만들지 않으면 된다.
객체 타입을 수정할 수 없게 만들면 부작용을 원천 차단할 수 있다.
값 타입은 불변 객체로 설계해야 한다.
publicclassJpaMain {publicstaticvoidmain(String[] args) {Address address =newAddress("city","street","10012");Member member =newMember();member.setUsername("member1");member.setAddress(address);em.persist(member);// city를 바꾸고 싶다면 setCity() 대신 // Address 객체를 새로 만들어서 통으로 갈아 끼운다.Address newAddress =newAddress("new city",address.getStreet(),address.getZipcode());member.setAddress(newAddress);tx.commit(); }}