일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 1차원 DP
- 2차원 dp
- 99클럽
- @Builder
- @GeneratedValue
- @GenericGenerator
- @NoargsConstructor
- @Transactional
- Actions
- Amazon EFS
- amazon fsx
- Android Studio
- ANSI SQL
- ApplicationEvent
- assertThat
- async/await
- AVG
- AWS
- Azure
- bind
- builder
- button
- c++
- c++ builder
- c03
- Callback
- case when
- CCW
- chat GPT
- CICD
- Today
- Total
기록
[자바 ORM 표준 JPA 프로그래밍] JPA 값 타입 - 기본 값 타입, 임베디드 타입, 값 타입 컬렉션 (9장) 본문
시작하면서
자바 ORM 표준 JPA 프로그래밍을 공부하면서 9장에서 다루는 "값 타입"에 대해 정리해보았습니다. 이번 글에서는 값 타입의 종류와 그 특징을 설명하며, 실제 개발에 어떤 점들을 고려해야 하는지 소개합니다.
값 타입이란?
JPA에서 값 타입은 int
, Integer
, String
처럼 단순한 값을 나타내는 자바 타입이나 객체를 말합니다. JPA에서는 이러한 값 타입을 크게 세 가지로 나눌 수 있습니다.
- 기본 값 타입: 자바에서 제공하는 기본 데이터 타입 (
int
,double
,Integer
,String
등) - 임베디드 타입 (복합 값 타입): 사용자가 직접 정의한 여러 속성을 하나로 묶은 값 타입
- 컬렉션 값 타입: 값 타입을 여러 개 저장하기 위해 컬렉션에 보관하는 방식입니다.
각 값 타입은 JPA에서 특정한 역할을 하고 제약이 있으므로, 이들을 잘 이해하고 적절하게 사용하는 것이 중요합니다.
값 타입 선택 가이드
- 기본 값 타입: 단순한 값을 저장할 때 사용합니다. 기본적으로 변경 가능하므로 복사해서 사용하는 것이 안전합니다.
- 임베디드 타입: 여러 필드를 묶어서 하나의 값 객체로 관리하고 싶을 때 사용합니다. 코드의 재사용성과 가독성을 높이는데 유용합니다.
- 컬렉션 값 타입: 값 타입을 하나 이상 관리해야 할 때 사용합니다. 식별자가 없고 변경 추적이 어렵기 때문에 주의해서 사용해야 합니다.
1. 기본 값 타입
기본 값 타입은 자바의 기본 타입과 유사하게 사용됩니다. 예를 들어, int
, double
같은 기본 타입은 절대 공유되지 않고 값 자체를 복사해서 사용합니다. 래퍼 클래스인 Integer
나 String
도 마찬가지입니다. 이러한 타입들은 객체이지만, 자바 언어 차원에서 기본 타입처럼 사용할 수 있어 JPA에서도 기본 값 타입으로 정의됩니다.
2. 임베디드 타입 (복합 값 타입)
2.1 임베디드 타입이란?
임베디드 타입은 새로운 값 타입을 직접 정의해서 사용할 수 있는 기능을 의미합니다. 예를 들어, 회원 엔티티가 이름
, 근무 시작일
, 근무 종료일
, 주소(도시, 번지, 우편번호)
같은 여러 필드를 가진다면, 이러한 속성들을 별도로 관리하기 위해 임베디드 타입으로 분리할 수 있습니다.
임베디드 타입을 정의하려면 @Embeddable
어노테이션을 사용하거나, 엔티티 내에 @Embedded
로 선언하면 됩니다. 이렇게 하면 코드의 재사용성과 가독성을 높일 수 있습니다.
2.2 임베디드 타입과 테이블 매핑
예를 들어, 회원 엔티티가 근무기간
과 집 주소
를 임베디드 타입으로 가지고 있다고 가정해 봅시다.
@Embeddable
public class Period {
private LocalDate startDate;
private LocalDate endDate;
// getters and setters
}
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
// getters and setters
}
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded
private Period workPeriod;
@Embedded
private Address homeAddress;
// getters and setters
}
위 코드에서 Period
와 Address
는 임베디드 타입으로 정의되어 Member
엔티티에 포함됩니다. 이를 통해 관련된 값들을 그룹화하여 깔끔하게 관리할 수 있습니다. 예를 들어, 위 구조를 사용하여 Member
테이블에 데이터가 저장될 때는 다음과 같은 INSERT 쿼리가 실행됩니다:
INSERT INTO Member (id, name, startDate, endDate, city, street, zipcode) VALUES (1, 'John Doe', '2023-01-01', '2023-12-31', 'Seoul', 'Gangnam-daero', '12345');
이렇게 Period
와 Address
의 필드들이 Member
테이블의 컬럼으로 매핑되어 저장됩니다.
2.3 @AttributeOverride로 속성 재정의하기
임베디드 타입의 속성을 재정의해야 할 경우 @AttributeOverride
어노테이션을 사용할 수 있습니다. 예를 들어, 회원 엔티티에 추가적인 주소 정보가 필요하다면 다음과 같이 매핑 정보를 재정의할 수 있습니다.
@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;
이렇게 하면 같은 Address
임베디드 타입을 재사용하면서도 다른 컬럼 매핑을 지정할 수 있습니다.
3. 값 타입과 불변 객체
3.1 값 타입의 공유 참조 문제
값 타입은 여러 엔티티에서 공유될 경우 위험할 수 있습니다. 예를 들어, 회원1과 회원2가 같은 주소 객체를 참조하고 있다면, 한쪽에서 주소를 변경할 경우 다른 쪽에도 영향을 미치게 됩니다. 이는 값 타입의 의도와 맞지 않으며, 예기치 못한 버그를 초래할 수 있습니다.
Member member1 = new Member();
member1.setHomeAddress(new Address("OldCity"));
System.out.println("회원1의 주소: " + member1.getHomeAddress().getCity()); // 출력: OldCity
Address address = member1.getHomeAddress();
address.setCity("NewCity");
Member member2 = new Member();
member2.setHomeAddress(address);
System.out.println("회원2의 주소: " + member2.getHomeAddress().getCity()); // 출력: NewCity
System.out.println("회원1의 주소: " + member1.getHomeAddress().getCity()); // 출력: NewCity (원치 않는 변경 발생)
위 코드에서 회원2에 새로운 주소를 설정하려고 했지만, 회원1의 주소도 함께 변경되는 문제가 발생합니다. 이러한 문제를 해결하기 위해 값 타입은 불변 객체로 설계하는 것이 좋습니다.
3.2 불변 객체로 만들기
값 타입을 불변하게 만들면 공유 참조로 인한 부작용을 방지할 수 있습니다. 불변 객체로 만들기 위해서는 객체 생성 이후에는 값을 변경할 수 없도록 설계합니다. 예를 들어, 모든 필드를 final
로 선언하고, setter 메서드를 제공하지 않으면 됩니다.
@Embeddable
public class Address {
private final String city;
private final String street;
private final String zipcode;
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
// getters only, no setters
}
4. 값 타입 컬렉션
값 타입을 하나 이상 저장해야 한다면 컬렉션에 보관할 수 있습니다. JPA에서는 이를 위해 @ElementCollection
과 @CollectionTable
어노테이션을 사용합니다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Embedded
private Address homeAddress;
@ElementCollection
@CollectionTable(name = "FAVORITE_FOODS", joinColumns = @JoinColumn(name = "member_id"))
@Column(name = "food_name")
private Set<String> favoriteFoods = new HashSet<>();
}
위 예시에서 favoriteFoods
는 회원이 좋아하는 음식을 저장하는 값 타입 컬렉션입니다. 값 타입 컬렉션은 기본적으로 LAZY
로딩 전략을 사용하며, 이를 통해 여러 값 타입을 손쉽게 관리할 수 있습니다.
4.1 값 타입 컬렉션의 제약사항
값 타입 컬렉션은 식별자가 없기 때문에 영속성 컨텍스트에서 관리하기가 어렵습니다. 따라서 값 타입 컬렉션은 상태를 변경하기보다는 통째로 삭제하고 다시 추가하는 방식으로 관리하는 것이 일반적입니다. 만약 식별자가 필요하거나 값의 변경을 자주 추적해야 한다면, 값 타입이 아닌 엔티티로 설계하는 것이 더 적합합니다.
마무리
JPA에서 값 타입을 사용하는 것은 객체지향적인 설계를 데이터베이스와 연결하는 중요한 방법입니다. 기본 값 타입, 임베디드 타입, 값 타입 컬렉션을 적절히 활용하면 보다 깔끔하고 유지보수하기 쉬운 코드를 작성할 수 있습니다. 하지만 값 타입의 특성을 잘 이해하고, 특히 공유 참조 문제와 불변 객체 설계에 유의해야 합니다.
'교육 > 책' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 스프링 데이터 JPA/성능 최적화 (12장-2) (0) | 2024.12.07 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 스프링 데이터 JPA 기능/쿼리 메서드, 페이징과 정렬, 벌크 연산 (12장-1) (0) | 2024.12.07 |
[자바 ORM 표준 JPA 프로그래밍] JPA의 프록시와 연관관계 관리: 지연 로딩, 영속성 전이, 고아 객체 (8장) (0) | 2024.12.01 |
[자바 ORM 표준 JPA 프로그래밍] JPA 복합 키 매핑: @IdClass vs @EmbeddedId, 조인컬럼과 조인테이블 (7장-2) (0) | 2024.12.01 |
[자바 ORM 표준 JPA 프로그래밍] JPA 고급 매핑: 상속 관계 매핑(7장-1) (0) | 2024.12.01 |