0. 개요
* 본 포스팅은 이전 포스팅과 이어지는 내용임을 알립니다.
- 이전 포스팅에서 값 타입에 대해서 알아보았다.
- 이번 포스팅에서는 값 타입을 사용할 때 발생하는 문제점과 이를 해결하기 위해 사용하는 불변 객체에 대해서 알아보자.
1. 값 타입을 사용하는 이유
- 이전 포스팅에서 값 타입에 대해서 배웠다.
- 굳이 값 타입을 사용하는 이유가 무엇일까 생각해볼 필요가 있다.
- 왜냐면 JPA를 사용하므로, 이미 객체지향적 프로그래밍을 달성했다고 볼 수 있기 때문이다.
a) 굳이 값 타입을 사용하는 이유
- 값 타입을 사용하는 이유는 객체의 내용을 조금이라도 더욱 간소화하기 위함이다.
- 그러므로 값 타입 또한 단순하고 간단한 구조를 이뤄야 한다.
- 더불어, 값 타입을 사용함에 있어서 그 안정성이 보장되어야 한다.
- 그러나 값 타입을 사용하는 과정에서 안정성의 문제가 있다.
- 이에 대해서 알아보도록 하자.
2. 값 타입 사용의 문제점 - 공유 참조
a) 공유 참조의 개념
- 값 타입 중 임베디드 타입을 사용하는 경우, 다수의 Entity에서 이를 공유(= 사용)할 수 있다.
- 여기서 공유의 의미는 동일한 임베디드 객체를 사용하여 값을 설정한다는 것이다.
- 이렇게 되면 어떤 문제가 발생할까?
b) 예시 코드
- 이전 포스팅에서 다음과 같은 임베디드 타입을 생성 및 적용하였다.
@Embeddable // 임베디드 값 타입을 정의한다는 어노테이션
public class Address {
private String city;
private String street;
private String zipcode;
public Address() {
}
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
// Getter & Setter 생략
}
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String name;
@Embedded
private Address homeAddress;
// Getter & Setter 생략
}
- 그렇다면 이 임베디드 타입을 이용해서 다음의 코드를 작성하였다고 해보자.
- 위의 코드를 분석해보면, 동일한 Address 객체를 사용하여 집 주소를 설정하였다.
- 그리고 member2만 도시의 주소를 수정하였다.
- 이 코드의 결과를 생각해보면 member2 객체만 도시가 Busan으로 값이 변경되어야 한다.
- 그러나 실제 실행 결과는 이와는 조금 다르다.
c) 실행 결과
- 위의 코드를 실행한 후, DB를 확인해보면 결과는 다음과 같다.
- 분명 위에서 member2 객체에 대해서만 도시의 주소를 Busan으로 변경했다.
- 그러나 member1의 도시 주소도 Busan으로 변경된 것을 확인할 수 있다.
- 이러한 버그는 따로 에러가 발생하는 것이 아니므로 발견하기 매우 힘들다.
- 특히, 실무에서 이와 같은 일이 발생했다면 간담이 서늘해진다.
- 왜 이와 같은 결과가 발생하는 것일까?
d) 공유 참조의 원인
- 위에서 사용한 값 타입(= Address) 객체는 하나의 메모리 주소를 가리킨다.
- 즉, member2.getHomeAddress().setCity()는 member1에서 사용한 것과 동일한 Address 객체의 값을 수정한다.
- 즉, member1과 member2는 동일한 Address 객체를 사용하므로 member2에서 변경한 값을 그대로 적용하는 것이다.
3. 다양한 해결 방법
a) 해결 방법(1) - 값 타입 대신 Entity로 선언
- 기본적으로 값 타입 객체는 다수의 Entity와 공유하여 사용하지 않는다.
- 위의 코드처럼 값 타입 객체를 공유하여 사용하고 싶다면 값 타입이 아니라 Entity로 정의하여 사용해야 한다.
- 즉, 값 타입이 아니라 Entity로 선언하여 사용한다면 이와 같은 문제는 발생하지 않는다.
b) 해결 방법(2) - 인스턴스 복사
- 값 타입을 반드시 사용해야 한다면 인스턴스 복사를 이용해 새로운 값 타입 객체를 생성하는 것을 권장한다.
- 이는 다음과 같이 작성할 수 있다.
- 이처럼 아예 새로운 인스턴스(= address2)를 생성하여 address1의 값을 복사해오는 방법을 사용할 수 있다.
- 그러나 이 방식은 그다지 효율적이지 않은 방식이다. 매번 다른 객체의 값을 가져와야 하기 때문이다.
- 더불어, 이 방식은 개발자의 실수를 예방할 수 있는 필터가 존재하지 않는다.
- 자바의 primitive 타입을 사용하는 경우, 위와 같은 방식으로 새로운 객체를 생성하면 다른 메모리 주소 값을 가진다. (이전 포스팅 참고)
- 그러나 address1과 address2는 객체이므로 동일한 메모리 주소 값을 가진다.
- 결국 여전히 객체의 공유 참조가 발생할 수 있는 방법이다.
- 그렇다면 이를 완벽하게 차단 및 예방할 수 있는 방법이 무엇일까?
4. 불변 객체(immutable Object)
a) 개념
- 위에서 언급한 값 타입 객체 사용의 문제점과 해결책을 종합해보면 다음과 같은 결론을 얻을 수 있다.
→ 애초에 값 타입 객체의 값을 변경할 수 없게 만들어야 한다.
- 이처럼 값을 수정할 수 없도록 설계된 값 타입을 불변 객체라고 한다.
- 불변 객체의 경우 Setter가 존재하지 않거나 private 접근 지정자를 가진 Setter만을 가진다.
- 즉, 생성 이후에는 절대 객체의 값을 변경할 수 없다.
- 다음 예시 코드를 통해 어떤 구조를 가지는지 알아보자.
- 먼저, Setter가 없는 구조다.
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
public Address() {
}
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
public String getCity() {return city;}
public String getStreet() {return street;}
public String getZipcode() {return zipcode;}
}
- 다음 코드는 private 접근 지정자를 사용한 Setter를 보유한 구조다.
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
public Address() {
}
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
public String getCity() {return city;}
public String getStreet() {return street;}
public String getZipcode() {return zipcode;}
private void setCity(String city) {this.city = city;}
private void setStreet(String street) {this.street = street;}
private void setZipcode(String zipcode) {this.zipcode = zipcode;}
}
b) 만약 값을 바꾸고 싶다면
- 불변 객체를 사용하는 경우, 값을 바꾸고 싶다면 새로운 객체를 생성하여 통째로 값을 바꾼다.
c) 결론
- 이처럼 불변 객체란 Setter를 사용하지 못도록 제약을 걸어서 문제를 해결하는 방식이다.
'Back-end > JPA 개념' 카테고리의 다른 글
21. 값 타입(4) - 값 타입과 컬렉션 (0) | 2022.04.19 |
---|---|
20. 값 타입(3) - 객체 비교와 equals() 재정의 (0) | 2022.04.18 |
18. 값 타입(1) - 개념 (0) | 2022.04.14 |
17. 고아 객체(Orphan) (0) | 2022.04.13 |
16. 영속성 전이(Cascade) (0) | 2022.04.12 |
댓글