본문 바로가기
자바/JPA

[JPA/Java] 값 타입 2 (불변 객체)

by drCode 2023. 3. 8.
728x90
반응형

값 타입은 복잡한 객체 세상을 조금이라도 단순화하려고 만든 개념인데,

따라서 값 타입은 단순하고 안전하게 다룰 수 있어야 한다.

 

예를 들어, 한 주소 객체를 선언 했는데, 선언한 주소를 두 명의 멤버에 넣고, 주소를 변경시키는 경우,

두 명의 멤버의 주소가 모두 바뀌게 된다.

 

JpaMain.java

Address address = new Address("oldHome", "oldStreet", "zipcoode");

Member member1 = new Member();
member1.setUserName("member1");
member1.setHomeAddress(address);
em.persist(member1);

Member member2 = new Member();
member2.setUserName("member2");
member2.setHomeAddress(address);
em.persist(member2);

member1.getHomeAddress().setCity("newHome");

 위 코드를 실행하면 member1, member2 모두 newHome으로 세팅되는 것을 확인할 수 있다.

 

Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (INSERT_MEMBER, createdDate, UPDATE_MEMBER, lastModifiedDate, city, street, zipCode, TEAM_ID, USERNAME, WORK_CITY, WORK_STREET, WORK_ZIP_CODE, endDate, startDate, MEMBER_ID) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (INSERT_MEMBER, createdDate, UPDATE_MEMBER, lastModifiedDate, city, street, zipCode, TEAM_ID, USERNAME, WORK_CITY, WORK_STREET, WORK_ZIP_CODE, endDate, startDate, MEMBER_ID) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    /* update
        hellojpa.Member */ update
            Member 
        set
            INSERT_MEMBER=?,
            createdDate=?,
            UPDATE_MEMBER=?,
            lastModifiedDate=?,
            city=?,
            street=?,
            zipCode=?,
            TEAM_ID=?,
            USERNAME=?,
            WORK_CITY=?,
            WORK_STREET=?,
            WORK_ZIP_CODE=?,
            endDate=?,
            startDate=? 
        where
            MEMBER_ID=?
Hibernate: 
    /* update
        hellojpa.Member */ update
            Member 
        set
            INSERT_MEMBER=?,
            createdDate=?,
            UPDATE_MEMBER=?,
            lastModifiedDate=?,
            city=?,
            street=?,
            zipCode=?,
            TEAM_ID=?,
            USERNAME=?,
            WORK_CITY=?,
            WORK_STREET=?,
            WORK_ZIP_CODE=?,
            endDate=?,
            startDate=? 
        where
            MEMBER_ID=?
3월 08, 2023 9:51:56 오전 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:h2:tcp://localhost/~/jpaStudy2]

Process finished with exit code 0

 

Member1, Member2 가 있으니

 

insert  문이 2번 나가는게 맞는데,  update문도 2번 나가게 되었다.

 

member1, member2 둘다 newHome 으로 세팅되었다.

 

이렇게 두 객체가 하나의 값을 같이 참조해버리니까 하나를 바꿀 때, 두 객체의 값이 모두 변하게 되는 현상이 발생한다.

 

이처럼 값 타입이 공유될 때, 값의 주소를 참조하게 되므로 값 변경 시 부작용이 발생한다.

 

임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험하다.

 

하지만 값 타입을 복사한다면 안전하게 사용이 가능하다.

 

값 복사

 

JpaMain.java

Address address = new Address("city", "street", "10000");

Member member = new Member();
member.setUserName("member1");
member.setHomeAddress(address);
em.persist(member);

// address를 새로 인스턴스를 만들지 않으면 아래 newCity를 넣을 때 두번의 update 쿼리가 나간다.
Address copyAddress = new Address(address.getCity(), address.getStreet(), address.getZipCode());

Member member2 = new Member();
member2.setUserName("member2");
member2.setHomeAddress(copyAddress);
em.persist(member2);

member.getHomeAddress().setCity("newCity");

 

아래는 실행 결과다.

 

Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (INSERT_MEMBER, createdDate, UPDATE_MEMBER, lastModifiedDate, city, street, zipCode, TEAM_ID, USERNAME, WORK_CITY, WORK_STREET, WORK_ZIP_CODE, endDate, startDate, MEMBER_ID) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (INSERT_MEMBER, createdDate, UPDATE_MEMBER, lastModifiedDate, city, street, zipCode, TEAM_ID, USERNAME, WORK_CITY, WORK_STREET, WORK_ZIP_CODE, endDate, startDate, MEMBER_ID) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    /* update
        hellojpa.Member */ update
            Member 
        set
            INSERT_MEMBER=?,
            createdDate=?,
            UPDATE_MEMBER=?,
            lastModifiedDate=?,
            city=?,
            street=?,
            zipCode=?,
            TEAM_ID=?,
            USERNAME=?,
            WORK_CITY=?,
            WORK_STREET=?,
            WORK_ZIP_CODE=?,
            endDate=?,
            startDate=? 
        where
            MEMBER_ID=?

insert는 2번, update는 1번 실행됐다.

 

이렇듯 값을 복사하여 사용한다면 두 가지 값이 모두 변하지 않는 모습을 확인할 수 있다.

 

이렇게 항상 값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용을 피할 수 있다.

 

임베디드 타입은 직접 정의한 값 타입이라 자바의 기본 타입이 아니라 객체 타입인데,

 

이런 객체 타입은 참조 값을 직접 대입하는 것을 막을 방법이 없다.

 

객체의 공유 참조는 피할 수 없다.

 

기본 타입은 값을 복사하지만, 객체 타입은 참조를 전달한다.

 

객체 타입을 불변 객체로 만들면 참조를 전달하는 것을 막을 수 있다.

  • 객체 타입을 수정할 수 없게 만들면 부작용을 원천 차단할 수 있다.
  • 값 타입은 불변 객체(immutable object)로 설계해야 한다.
  • 불변 객체 : 생성 시점 이후 절대 값을 변경할 수 없는 객체이다.
  • 생성자로만 값을 설정하고 수정자(Setter)를 만들지 않으면 된다,
  • 참고 : Integer, String은 자바가 제공하는 대표적인 불변 객체이다.

 

728x90
반응형

댓글