개요
객체지향적인 어플리케이션을 설계하기 위해선 자율성을 가지는 객체들의 유기적인 협력 관계를 잘 설계해야 한다.
따라서 도메인 로직을 객체지향적으로 설계하기 위해선 엔티티 객체 내부에서도 객체 분리가 이뤄져야 한다.
JPA에서 제공하는 값 타입 기능을 이용하면 엔티티 객체 내부에서도 객체 분리를 할 수 있다.
값 타입
엔티티 객체 내부에서 새로운 값 타입을 정의해 사용할 수 있는데, JPA에선 이것을 임베디드 타입이라고 한다.
Member 엔티티의 주소와 근무 기간을 값 타입으로 분리해 보자.
Member.java
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "MEMBER_ID")
private Long id;
private String name;
//기간 값 타입
@Embedded
private Period period;
//주소 값 타입
@Embedded
private Address address;
// getter
}
- 회원 엔티티의 주소와 기간을 값 타입으로 분리했다.
Period.java
@Embeddable
public class Period {
private LocalDateTime startDate;
private LocalDateTime endDate;
public boolean isWork(Date date) {
// 기간의 책임 메서드
}
// getter, hashCode, equals
}
Address.java
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
// getter, hashCode, equals
}
- 위 Period처럼 기간이 해야 할 책임 작업을 값 타입을 이용해 분리할 수 있다.
위처럼 Member 내 객체 분리를 함으로써 응집력 있게 만들 수 있다.
값 타입과 테이블 매핑
값 타입은 엔티티의 값일 뿐이다. 따라서 아래와 같이 해당 타입이 속한 엔티티 테이블에 값들이 그대로 매핑된다.
만약 JPA를 사용하지 않고 값 타입을 이용하려고 했다면, 엔티티 테이블 하나에 여러 클래스를 매핑해야 했을 것이다. JPA는 이 작업을 매우 간편하게 해 준다.
값 타입과 불변 객체
값 타입은 객체를 값으로 보는 것이다. 물론 객체이므로 여러 기본 타입이나 다른 값 타입의 합으로 이뤄져 있겠지만, 이 합을 하나의 값으로 본다. 따라서 값 타입 객체 공유 참조 관련 문제가 일어나지 않도록 해야 한다.
이것이 equals와 hashCode를 재정의해야 하는 이유이다. Address 객체를 불변으로 만들어보자.
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
public Address() {
}
public Address(final String city, final String street, final String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
// getter
@Override
public boolean equals(final Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
Address address = (Address) object;
return Objects.equals(city, address.city) && Objects.equals(street,
address.street) && Objects.equals(zipcode, address.zipcode);
}
@Override
public int hashCode() {
return Objects.hash(city, street, zipcode);
}
}
setter를 만들지 않고, equals와 hashCode 메서드를 재정의함으로써 Address 객체를 값처럼 이용할 수 있는 VO(Value Object)로 만들었다. 이제 값을 수정할 수 없기 때문에 공유 참조와 같은 부작용이 발생하지 않는다.
만약 Member의 주소를 수정하고 싶다면, 해당 객체의 값을 바꾸는 것이 아닌 새로운 Address 객체를 생성해야 한다.
값 타입 컬렉션
만약 좋아하는 음식들, 주소 기록 등 여러 값 타입을 가져야 한다면 어떻게 해야 할까?
값 타입을 컬렉션으로 사용할 수 있는 @ElementCollection과 @CollectionTable이 있다.
하지만 값 타입은 앞서 말했듯이 객체가 아닌 값이다. 따라서 식별자가 없어 CRUD 작업을 하는 것이 번거롭고, 비효율적인 작업을 거쳐 수행하게 된다. 따라서 값 타입 컬렉션을 이용하기보단 일대다 관계를 고려하는 것이 좋다.
영속성 전이와 고아 객체 제거 기능을 적용하면, 위 기록에서 말했듯 일(부모)과 다(자식) 간의 생명주기를 맞출 수 있다. 또한 별도의 식별자도 가지기 때문에 CRUD 작업을 훨씬 수월하게 수행할 수 있다.
엔티티 타입과 차이점
엔티티 타입과 값 타입의 차이점을 정리하면 아래와 같다.
- 엔티티와 달리 값 타입은 식별자가 없다. 오직 값으로 사용된다.
- 생명 주기를 엔티티에 의존한다. (엔티티 - 부모, 값 타입 - 자식)
- 엔티티와 달리 공유했을 때 공유 참조 문제가 생길 수 있다.
- 오직 하나의 주인만이 관리하는 것이 안전하다.
- 불변 객체로 만드는 것이 안전하다.
마무리
값 타입에서 가장 중요한 개념은 VO(Value Object) 일 것 같다. VO에 대해 더 자세하게 공부해 보고 기록을 남겨야 할 것 같다.
잘못된 정보에 대한 지적이나 피드백은 환영입니다. 감사합니다.
참고
'Backend > JPA' 카테고리의 다른 글
[JPA] 영속성 전이와 고아 객체(Cascade, Orphan) (0) | 2024.05.17 |
---|---|
[JPA] 프록시와 연관관계 관리(즉시로딩, 지연로딩) (0) | 2024.05.15 |
[JPA] 다대다 연관관계 매핑 (0) | 2024.05.10 |
[JPA] 다대일 연관관계 매핑 (0) | 2024.05.08 |
[JPA] 영속성 관리(영속성 컨텍스트, 엔티티 매니저) (0) | 2024.05.04 |