223 lines
7.4 KiB
Markdown
223 lines
7.4 KiB
Markdown
아래는 "값 타입과 임베디드 타입의 활용"에 대해 설명하는 글입니다. JPA의 값 타입(Value Type)과 임베디드 타입(Embedded Type)을 실무 관점에서 다루며, 예시를 포함해 구체적으로 작성했습니다.
|
|
|
|
---
|
|
|
|
### 값 타입과 임베디드 타입의 활용
|
|
|
|
JPA에서 값 타입(Value Type)은 엔티티와 달리 독립적인 생명주기를 가지지 않고, 특정 엔티티에 속해 그 엔티티와 함께 생성되고 소멸되는 데이터입니다. 값 타입은 단순 데이터(예: 기본 타입)와 복합 데이터(임베디드 타입)로 나뉘며, 특히 임베디드 타입은 객체지향 설계의 재사용성과 가독성을 높이는 데 유용합니다. 이를 잘 활용하면 코드 중복을 줄이고 도메인 모델을 더 풍부하게 표현할 수 있습니다.
|
|
|
|
#### 값 타입의 종류
|
|
1. **기본 값 타입 (Basic Value Type)**
|
|
- 자바의 기본형(`int`, `boolean`)과 래퍼 클래스(`Integer`, `String`) 등.
|
|
- 엔티티 필드에 직접 매핑되며, 데이터베이스 컬럼에 저장됩니다.
|
|
2. **임베디드 타입 (Embedded Type)**
|
|
- 사용자 정의 클래스를 값 타입으로 사용하며, `@Embeddable`과 `@Embedded`로 정의합니다.
|
|
- 여러 필드를 묶어 논리적 단위를 형성합니다.
|
|
3. **컬렉션 값 타입 (Collection Value Type)**
|
|
- `List`, `Set` 등으로 값 타입을 여러 개 관리합니다(여기서는 생략하고 별도 장에서 다룰 수 있음).
|
|
|
|
#### 임베디드 타입의 정의와 활용
|
|
임베디드 타입은 `@Embeddable`로 정의된 클래스를 엔티티 내에서 `@Embedded`로 사용합니다. 주로 주소, 기간, 좌표 같은 복합 데이터를 표현할 때 유용합니다.
|
|
|
|
#### 예시 1: 주소(Address) 임베디드 타입
|
|
```java
|
|
import jakarta.persistence.Embeddable;
|
|
|
|
@Embeddable
|
|
public class Address {
|
|
private String city;
|
|
private String street;
|
|
private String zipcode;
|
|
|
|
// JPA를 위한 기본 생성자
|
|
public Address() {}
|
|
|
|
public Address(String city, String street, String zipcode) {
|
|
this.city = city;
|
|
this.street = street;
|
|
this.zipcode = zipcode;
|
|
}
|
|
|
|
// getter만 제공해 불변성 보장
|
|
public String getCity() { return city; }
|
|
public String getStreet() { return street; }
|
|
public String getZipcode() { return zipcode; }
|
|
}
|
|
```
|
|
|
|
```java
|
|
import jakarta.persistence.Entity;
|
|
import jakarta.persistence.Id;
|
|
import jakarta.persistence.Embedded;
|
|
|
|
@Entity
|
|
public class Member {
|
|
@Id
|
|
private Long id;
|
|
private String name;
|
|
|
|
@Embedded
|
|
private Address address;
|
|
|
|
public Member() {}
|
|
public Member(Long id, String name, Address address) {
|
|
this.id = id;
|
|
this.name = name;
|
|
this.address = address;
|
|
}
|
|
|
|
// getter, setter
|
|
public Long getId() { return id; }
|
|
public String getName() { return name; }
|
|
public Address getAddress() { return address; }
|
|
public void setAddress(Address address) { this.address = address; }
|
|
}
|
|
```
|
|
|
|
- **테이블 구조**:
|
|
```sql
|
|
CREATE TABLE member (
|
|
id BIGINT PRIMARY KEY,
|
|
name VARCHAR(255),
|
|
city VARCHAR(255),
|
|
street VARCHAR(255),
|
|
zipcode VARCHAR(255)
|
|
);
|
|
```
|
|
- **설명**: `Address`는 독립적인 엔티티가 아니라 `Member`에 속한 값 타입입니다. 데이터베이스에서는 `Member` 테이블에 포함되며, 별도 테이블이 생성되지 않습니다.
|
|
|
|
#### 예시 2: 기간(Period) 임베디드 타입
|
|
```java
|
|
import jakarta.persistence.Embeddable;
|
|
import java.time.LocalDate;
|
|
|
|
@Embeddable
|
|
public class Period {
|
|
private LocalDate startDate;
|
|
private LocalDate endDate;
|
|
|
|
public Period() {}
|
|
public Period(LocalDate startDate, LocalDate endDate) {
|
|
this.startDate = startDate;
|
|
this.endDate = endDate;
|
|
}
|
|
|
|
public LocalDate getStartDate() { return startDate; }
|
|
public LocalDate getEndDate() { return endDate; }
|
|
}
|
|
```
|
|
|
|
```java
|
|
import jakarta.persistence.Entity;
|
|
import jakarta.persistence.Id;
|
|
import jakarta.persistence.Embedded;
|
|
|
|
@Entity
|
|
public class Project {
|
|
@Id
|
|
private Long id;
|
|
private String title;
|
|
|
|
@Embedded
|
|
private Period duration;
|
|
|
|
public Project() {}
|
|
public Project(Long id, String title, Period duration) {
|
|
this.id = id;
|
|
this.title = title;
|
|
this.duration = duration;
|
|
}
|
|
|
|
// getter, setter
|
|
}
|
|
```
|
|
|
|
- **테이블 구조**:
|
|
```sql
|
|
CREATE TABLE project (
|
|
id BIGINT PRIMARY KEY,
|
|
title VARCHAR(255),
|
|
start_date DATE,
|
|
end_date DATE
|
|
);
|
|
```
|
|
|
|
#### 임베디드 타입의 장점
|
|
1. **재사용성**: `Address`나 `Period` 같은 타입을 여러 엔티티에서 재사용 가능.
|
|
2. **가독성**: 관련 필드를 논리적으로 묶어 도메인 의미를 명확히 표현.
|
|
3. **불변성**: setter를 제거하고 생성자로만 값을 설정하면 부작용 방지.
|
|
4. **코드 간소화**: 공통 로직(예: 주소 유효성 검사)을 임베디드 타입에 추가 가능.
|
|
|
|
#### 활용 팁
|
|
- **컬럼명 커스터마이징**: 동일한 임베디드 타입을 여러 번 사용할 경우 충돌을 피하기 위해 `@AttributeOverrides`를 사용합니다.
|
|
```java
|
|
@Entity
|
|
public class Member {
|
|
@Id
|
|
private Long id;
|
|
|
|
@Embedded
|
|
private Address homeAddress;
|
|
|
|
@Embedded
|
|
@AttributeOverrides({
|
|
@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;
|
|
|
|
// 생성자, getter
|
|
}
|
|
```
|
|
- `homeAddress`와 `workAddress`가 동일 테이블에서 다른 컬럼명으로 매핑됩니다.
|
|
|
|
- **null 처리**: 임베디드 타입이 `null`이면 모든 필드가 `null`로 저장됩니다. 이를 활용해 선택적 데이터를 표현할 수 있습니다.
|
|
- **비즈니스 로직 추가**: 예를 들어, `Period`에 `isActive()` 메서드를 추가해 현재 날짜가 기간 내인지 확인 가능.
|
|
|
|
#### 주의사항
|
|
- **생명주기 의존성**: 임베디드 타입은 엔티티에 종속적이므로 독립적으로 저장하거나 조회할 수 없습니다.
|
|
- **불변성 권장**: 값 타입은 변경 시 기존 객체를 교체하는 방식으로 관리해야 부작용을 줄입니다(예: `setAddress(new Address(...))`).
|
|
- **성능 고려**: 임베디드 타입이 지나치게 복잡하면 테이블 구조가 비대해질 수 있으니 적절히 분리합니다.
|
|
|
|
#### 실무 활용 예시
|
|
주문 엔티티에서 배송 정보와 결제 정보를 임베디드 타입으로 분리:
|
|
```java
|
|
@Embeddable
|
|
public class DeliveryInfo {
|
|
private String receiver;
|
|
private String address;
|
|
|
|
// 생성자, getter
|
|
}
|
|
|
|
@Embeddable
|
|
public class PaymentInfo {
|
|
private String method;
|
|
private int amount;
|
|
|
|
// 생성자, getter
|
|
}
|
|
|
|
@Entity
|
|
public class Order {
|
|
@Id
|
|
private Long id;
|
|
|
|
@Embedded
|
|
private DeliveryInfo delivery;
|
|
|
|
@Embedded
|
|
private PaymentInfo payment;
|
|
|
|
// 생성자, getter
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
값 타입과 임베디드 타입은 JPA에서 도메인 모델을 풍부하게 만들고, 객체지향 설계를 데이터베이스에 효과적으로 반영하는 도구입니다. 이를 활용하면 코드의 응집도를 높이고 유지보수성을 강화할 수 있습니다. 다음 장에서는 성능 최적화와 관련된 주제를 다뤄보겠습니다.
|
|
|
|
---
|
|
|
|
책의 흐름에 맞는지, 추가 예시나 수정이 필요하면 말씀해 주세요! |