값 타입과 불변 객체
값 타입과 불변 객체
값 타입은 복잡한 객체 세상을 조금이라도 단순화하려고 만든 개념이다.
따라서 값 타입은 단순하고 안전하게 다룰 수 있어야 한다.
값 타입 공유 참조
예시
JpaMain
현재 member1과 member2는 같은 address를 쓰고 있다.
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Address address = new Address("seoul", "street", "10000");
// 현재 member1과 member2는 같은 address를 쓰고 있다.
Member memberA = new Member();
memberA.setUsername("member1");
memberA.setHomeAddress(address);
em.persist(memberA);
Member memberB = new Member();
memberB.setUsername("member2");
memberB.setHomeAddress(address);
em.persist(memberB);
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
실행하면
02:14:25.917 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert inflearn.exjpa.jpaExample.Member
*/ insert
into
Member
(city, street, zipcode, USERNAME, endDate, startDate, MEMBER_ID)
values
(?, ?, ?, ?, ?, ?, ?)
02:14:25.921 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert inflearn.exjpa.jpaExample.Member
*/ insert
into
Member
(city, street, zipcode, USERNAME, endDate, startDate, MEMBER_ID)
values
(?, ?, ?, ?, ?, ?, ?)
INSERT 쿼리 2번 나가고
DB 확인하면
member1과 member2 가 둘 다 같은 주소를 들고 있다.
이 때 변경!
(의도: 첫번째 멤버 것만 바꾸고 싶음)
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Address address = new Address("seoul", "street", "10000");
// 현재 member1과 member2는 같은 address를 쓰고 있다.
Member memberA = new Member();
memberA.setUsername("member1");
memberA.setHomeAddress(address);
em.persist(memberA);
Member memberB = new Member();
memberB.setUsername("member2");
memberB.setHomeAddress(address);
em.persist(memberB);
// 여기서 변경 (의도: 첫번째 멤버 것만 바꾸고 싶음)
memberA.getHomeAddress().setCity("newCity");
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
실행 결과를 확인해보면
02:19:25.553 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* update
inflearn.exjpa.jpaExample.Member */ update
Member
set
city=?,
street=?,
zipcode=?,
USERNAME=?,
endDate=?,
startDate=?
where
MEMBER_ID=?
02:19:25.559 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* update
inflearn.exjpa.jpaExample.Member */ update
Member
set
city=?,
street=?,
zipcode=?,
USERNAME=?,
endDate=?,
startDate=?
where
MEMBER_ID=?
업데이트 쿼리 두 번 날아간다.
DB 확인해보면
member1, member2 모두 newCity로 바뀌어 있다....
이런 버그는 잡기 정말 어렵다.
해결책
같이 공유해서 쓰고 싶다면 엔티티로 만들어야 한다
값 타입 복사
예시
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Address address = new Address("seoul", "street", "10000");
// 현재 member1과 member2는 같은 address를 쓰고 있다.
Member memberA = new Member();
memberA.setUsername("member1");
memberA.setHomeAddress(address);
em.persist(memberA);
//해결책
Address copyAddress = new Address(address.getCity(), address.getStreet(), address.getZipcode());
Member memberB = new Member();
memberB.setUsername("member2");
memberB.setHomeAddress(copyAddress); // 여기 copyAddress 대입.
em.persist(memberB);
// 변경 (의도: 첫번째 멤버 것만 바꾸고 싶음)
memberA.getHomeAddress().setCity("newCity");
// 이 때는 제대로 첫 번째 아이만 변경됨.
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
실행 확인해보면
02:32:40.465 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* update
inflearn.exjpa.jpaExample.Member */ update
Member
set
city=?,
street=?,
zipcode=?,
USERNAME=?,
endDate=?,
startDate=?
where
MEMBER_ID=?
update 쿼리 한 번 나갔고,
DB 확인하면
의도대로 하나만 바뀌어 있음을 확인할 수 있다.
객체 타입의 한계
먼저 상황 예시부터
복사해서 써야 하는데, 누군가 실수로 그냥
Address address = new Address("seoul", "street", "10000");
Member memberA = new Member();
memberA.setUsername("member1");
memberA.setHomeAddress(address);
em.persist(memberA);
//해결책
Address copyAddress = new Address(address.getCity(), address.getStreet(), address.getZipcode());
Member memberB = new Member();
memberB.setUsername("member2");
memberB.setHomeAddress(address); // 그대로 넣어버림
em.persist(memberB);
memberA.getHomeAddress().setCity("newCity");
이걸 막을 수 있는 방법이 없다.
해결 방안
불변 객체
수정을 하려면 하나를 새로 만들어주거나 하는 방법을 활용
불변이라는 작은 제약으로
부작용이라는 큰 재앙을 막을 수 있다.
여기까지