본문 바로가기
Framework and Tool/JPA

JPA - 값 타입 - 불변객체

by ocwokocw 2021. 7. 22.

- 참조: 자바 ORM 표준 JPA 프로그래밍

- 불변객체

값 타입을 여러 엔티티에서 공유해서 사용하면 위험하다. 

 

public static void save(EntityManager em) {
	
	EntityTransaction tx = em.getTransaction();
	tx.begin();
	
	Member member1 = new Member();
	
	member1.setId("User#1");
	
	Address companyAddr = new Address();
	
	companyAddr.setCity("City#1");
	companyAddr.setStreet("Street#1");
	
	member1.setCompanyAddress(companyAddr);
	
	em.persist(member1);
	
	Member member2 = new Member();
	
	member2.setId("User#2");
	em.persist(member2);
	
	Address member1CompanyAddr = member1.getCompanyAddress();
	
	member1CompanyAddr.setStreet("Street#2");
	member2.setCompanyAddress(companyAddr);
	
	tx.commit();
	em.close();
}

 

위의 코드의 의도는 User#1 의 주소에서 Street 만 Street#2 로 변경하여 User#2 의 주소를 갱신하는것이다. 기대한 결과는 User#1 은 City#1, Street#1 의 주소를, User#2 는 City#1, Street#2 의 주소를 가진다는것이지만 조회하면 결과는 아래와 같이 나온다.

User#1과 User#2 의 Street 가 모두 갱신되어 버린다. 두 회원이 같은 주소의 인스턴스를 참조해버렸기 때문에 영속성 컨텍스트는 두 회원의 주소가 모두 변경된것으로 보고 SQL 을 수행한다.

 

Hibernate: 
    /* update
        com.example.demo.test1.Member */ update
            member 
        set
            company_city=?,
            company_street=?,
            company_zipcode=?,
            city=?,
            street=?,
            zipcode=?,
            user_name=? 
        where
            user_id=?
Hibernate: 
    /* update
        com.example.demo.test1.Member */ update
            member 
        set
            company_city=?,
            company_street=?,
            company_zipcode=?,
            city=?,
            street=?,
            zipcode=?,
            user_name=? 
        where
            user_id=?

 

위와 같은 공유참조로 인해 일어나는 버그는 원인파악이 상당히 힘들다. 이를 방지하기 위해 값 타입들은 복사하여 사용하는것이 좋다.


- 값 타입 복사

자신을 복제하는 clone 메소드를 만들어서 회원2 의 주소만 갱신해보도록 하자.

 

@Override
public Address clone() throws CloneNotSupportedException {
	return (Address) super.clone();
}

.............

Address member1CompanyAddr = member1.getCompanyAddress();
Address member2CompanyAddr = member1CompanyAddr.clone();

member2CompanyAddr.setStreet("Street#2");
member2.setCompanyAddress(member2CompanyAddr);

 

Java 에서는 객체를 할당하면 주소는 참조하는데, clone 을 하면 복제를 하므로 다른 주소값을 참조한다. 따라서 복제한 객체를 수정하여도 원본은 갱신되었다고 판단하지 않는다.

값 타입은 복사를 해서 넘기면 되겠네 라고 생각할 수 있다. 물론 맞는말이다. 코드를 사용하는 모든 사용자들이 복사를 해서 넘기면 원래 의도대로 값 타입을 사용하게 된다. 하지만 이는 불확실하며 나중에 누군가는 이를 인지하지 못할 수 있다. 보다 확실한 방법은 setter 를 제공하지 않는것이다.


- 불변 객체(Immutable Object)

위와 같은 문제를 해결하기 위해 불변 객체가 등장한다. 불변 객체를 처음들어봤다 하더라도 우리는 이미 불변객체를 사용해봤을 가능성이 크다.

 

우리는 Integer 를 사용할 때, Integer 를 선언해놓고 setter 를 이용해서 값을 변경하지 않는다. 대신 새로운 Integer 값을 생성한다.

 

불변 객체를 가장 직관적으로 구현하는 법은 setter 를 제공하지 않고 생성자로만 값을 설정할 수 있게 제공하는것이다. Address 를 불변객체로 만들려면 아래와 같이 만든다.

 

@Embeddable
public class Address implements Cloneable{

	@Column(name = "city")
	private String city;
	private String street;
	private String zipcode;
	
	public Address() {
		super();
	}
	
	public Address(String city, String street, String zipcode) {
		super();
		this.city = city;
		this.street = street;
		this.zipcode = zipcode;
	}

- 마치면서

이번 챕터는 JPA 의 영역이라기 보다 effective java 같은 느낌이 있다. 기초적인 내용이긴하지만 엔티티와 값 타입을 구별할 줄 아는것은 중요하다. 

댓글