💡 코드가 보이지 않으시다면 드래그 혹은 오른쪽 아래 🌜 아이콘을 눌러 테마 색을 변경해주세요.
안녕하세요!
키크니 개발자 입니다. 🦒
임베디드 타입을 다시 복습하고자 정리했습니다.
임베디드 타입(Embedded Type)이란?
- 복합 값 타입을 의미합니다.
- 새로운 값 타입을 직접 정의할 수 있습니다.
- JPA는 임베디드 타입(embedded type)이라고 불려집니다.
- 주로 기본 값 타입을 모아서 만들어서 복합 값 타입이라고도 하며, int, String과 같은 값 타입을 의미합니다.
💡 JPA의 데이터 타입 분류
엔티티 타입
- @Entity로 정의하는 객체입니다.
- 데이터가 변해도 식별자로 지속해서 추적 가능합니다.
- 예)회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식 가능
값 타입
- int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체를 의미합니다.
- 식별자가 없고 값만 있으므로 변경시 추적 불가합니다.
- 예) 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체
- 값 타입 분류
- 기본 값 타입
- 자바 기본 타입(int, double)
- 래퍼 클래스(Integer, Long)
- String
- 임베디드 타입
- 컬렉션 값 타입
- 기본 값 타입
임베디드 타입 사용법
- @Embeddable : 값 타입을 정의하는 곳에 표시 (임베디드 타입을 사용하기 위해 생성한 Class에 표시)
- @Embedded : 값 타입을 사용하는 곳에 표시 (임베디드 타입을 사용하는 Entity에 표시)
임베디드 타입의 장점
- 재사용이 가능하다. (Period, Address 정보는 Member 외에도 다른 곳에서 사용할 수 있습니다.)
- 높은 응집도를 가집니다.
- Period.isWork() 처럼 해당 값 타입만 사용하는 의미 있는 메소드를 만들 수 있습니다. (현재 근무기간에 속하는지에 대한 의미있는 메소드를 뽑을 수 있으며 객체지향적으로 접근할 수 있습니다.)
- 임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티 티에 생명주기를 의존합니다. (엔티티가 생성될 때 임베디드 타입에도 값이 들어오고, 엔티티 사용이 끝났을 떄 값 타입도 함께 사용이 끝납니다.)
임베디드 타입 예시
아래 Member 엔티티는 처음에 7개의 필드를 갖고 있었는데 임베디드 타입을 사용하여 4개의 필드로 줄인 것을 확인할 수 있습니다.
workPeriod (startDate, endDate)
homeAddress (city, street, zipcode)
임베디드 타입 특징
- 임베디드 타입은 엔티티의 값일 뿐입니다.
- 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블에는 변화가 없습니다.
- 객체와 테이블을 아주 세밀하게 매핑하는 것이 가능합니다.
- 잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많습니다. (Address, Period 등 class로 뺴놓으면 활용할 수 있는 것이 많습니다. 이러한 value 타입들이 많아져 클래스 수가 더 많다고 볼 수 있습니다.)
- 도메인의 언어를 공통으로 맞출 수 있다는 장점이 있습니다. (예 : Address를 사용할 때에는 city, street, zipcode 필드를 고정으로 사용합니다.)
- 임베디드 클래스 안에 entity를 넣어서 사용할 수 있습니다.
@Entity
public class Member extends BaseEntity {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@Embedded // (1)
private Period workPeriod;
@Embedded // (1)
private Address homeAddress;
}
(1) @Embedded : 임베디드 타입을 사용하는 entity에 사용합니다.
@Getter
@Setter
@NoArgsConstructor // (1)
@AllArgsConstructor
@Embeddable // (2)
public class Address {
private String city;
private String street;
private String zipcode;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Embeddable // (2)
public class Period {
private LocalDateTime startDate;
private LocalDateTime endDate;
public boolean isWork() {
// 현재 일하고 있는지에 대한 로직
}
}
(1) 임베디드 타입을 사용할 때에는 기본 생성자가 필수입니다.
(2) @Embeddable : 임베디드 타입인 class에 표시해줍니다.
Member member = new Member();
member.setUsername("hello");
member.setHomeAddress(new Address("city", "street", "zipcode"));
member.setWorkPeriod(new Period());
em.persist();
member를 persist하면 임베디드 타입이 없는 엔티티 생성 쿼리문과 같은 쿼리문을 확인할 수 있습니다.
만약 하나의 엔티티에 같은 임베디드 타입을 사용하고 싶을 때에는?
@AttributeOverride를 사용합니다.
@AttributeOverride 적용 전
@Entity
public class Member extends BaseEntity {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@Embedded
private Period workPeriod;
@Embedded
private Address homeAddress;
@Embedded
private Address workAddress;
}
homeAddress와 workAddress의 컬럼명이 중복되므로 Repeated column in mapping for entity error가 발생합니다.
@AttributeOverride 적용 후
@Entity
public class Member extends BaseEntity {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@Embedded
private Period workPeriod;
@Embedded
private Address homeAddress;
@Embedded
@AttrubyteOverrides({ // (1)
@AttributeOverride(name="city",
column=@column(name="WORK_CITY")),
@AttributeOverride(name="street",
column=@column(name="WORK_STREET")),
@AttributeOverride(name="zipcode",
column=@column(name="WORK_ZIPCODe"))
})
private Address workAddress;
}
(1) 많이 사용하지는 않지만 위의 코드처럼 컬럼 명을 재정의 해주면 됩니다.
임베디드 타입에 Null을 넣게 되면?
member.setAddress(null); // null 입력
em.persist(member);
임베디드 타입(Address)안에 필드들(city, zipcode, street)도 모두 null값이 됩니다.
임베디드 타입 사용시 주의해야할 점
임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 사이드 이펙트로 인해 부작용이 발생할 수 있습니다.
Address address = new Address("city", "street", "zipcode");
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(address);
em.persist(member);
Member member2 = new Member();
member.setUsername("member2");
member.setHomeAddress(address);
em.persist(member2);
member.getHomeAddress().setCity("newCity"); // (1)
(1) member의 address city를 newCity로 변경 바꾸려고 한 것이지만,
member2의 address city도 newCity로 같이 변경됩니다.
이를 해결하려면 어떻게 해야하는가?
Address의 Setter를 구현하지 않거나, private으로 구현합니다.
- 객체 타입을 수정할 수 없게 만들어 부작용을 원천에 차단합니다.
- 값 타입은 불변 객체로 설계해야 합니다.
- 생성자로만 값을 설정하고 수정자(Setter)를 만들지 않으면 됩니다.
- 참고 : Integer, String은 자바가 제공하는 대표적인 불변 객체를 의미합니다.
불변 객체란? 생성 시점 이후 절대 값을 변경할 수 없는 객체를 의미합니다.
⭐️ 참고한 곳
자바 ORM 표준 JPA 프로그래밍 - 기본편
https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
배워야 할 것이 더 많은 주니어 개발자입니다. 🐣
내용 전달보다는 정리를 목적으로 포스팅을 하고 있습니다.
잘못 된 내용이나 부족한 부분은 댓글로 주시면 감사드리겠습니다.
'개발공부 > DATABASE' 카테고리의 다른 글
[DynamoDB] DyanamoDB의 기본 개념 (0) | 2022.09.04 |
---|---|
[JPA] 프록시와 지연로딩/즉시로딩의 관계 (0) | 2022.06.27 |
[JPA] 상속관계 매핑 (조인 전략, 단일테이블 전략, 구현클래스마다 테이블 전략) (0) | 2022.06.14 |
[JPA] 영속성 컨텍스트란? 그리고 영속성 관리 (0) | 2022.06.11 |