코드 그라데이션

값 타입과 불변 객체 본문

Spring/JPA 공부

값 타입과 불변 객체

완벽한 장면 2023. 8. 26. 02:33

값 타입과 불변 객체

 

값 타입은 복잡한 객체 세상을 조금이라도 단순화하려고 만든 개념이다.
따라서 값 타입은 단순하고 안전하게 다룰 수 있어야 한다.

 

 

값 타입 공유 참조

 

예시

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");

 

이걸 막을 수 있는 방법이 없다.

 

 

 

 

해결 방안

불변 객체

 

수정을 하려면 하나를 새로 만들어주거나 하는 방법을 활용

 

 

불변이라는 작은 제약으로 
부작용이라는 큰 재앙을 막을 수 있다.

 

여기까지

728x90

'Spring > JPA 공부' 카테고리의 다른 글

값 타입 컬렉션  (0) 2023.08.27
값 타입의 비교  (0) 2023.08.27
임베디드 타입(복합 값 타입)  (0) 2023.08.26
기본 값 타입  (0) 2023.08.26
실전 예제 - 5. 연관관계 관리  (0) 2023.08.25
Comments