코딩공작소
JPA 프로그래밍 학습 정리 (6) - 값 타입 본문
JPA에서는 데이터 타입이 2개 있다.
- 엔티티 타입
- 값 타입
값 타입
값 타입은 2가지 타입이 있다.
- 기본값 타입
- 임베디드 타입(복합 값 타입)
✔️ 기본 값 타입은 말 그대로, String, int 등의 기본 타입이다.
✔️ 새로운 값 타입을 직접 정의하고 사용해야 할 때 임베디드 타입을 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@Entity
public class Member {
@Embedded Period workPeriod; // 근무 기간
@Embedded Address homeAddress; // 집 주소
}
@Embeddable
public class Period {
@Temporal(TemporalType.DATE) java.util.Date startDate;
@Temporal(TemporalType.DATE) java.util.Date endDate;
// ..
public boolean isWork(Date date) {
//.. 값 타입을 위한 메소드를 정의할 수 있다.
}
}
@Embeddable
public class Address {
@Column(name = "city") // 매핑할 컬럼 정의 가능
}
|
cs |
: @Embeddable : 값 타입을 정의하는 곳에 표시
: @Embedded : 값 타입을 사용하는 곳에 표시
→ 임베디드 타입은 기본 생성자가 필수다.
✔️ 임베디드 타입은 값 타입을 포함하거나 엔티티를 참조할 수 있다.
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
|
@Entity
public class Member {
@Embedded Address address;
@Embedded PhoneNumber phoneNumber;
//...
}
@Embeddable
public class Address{
@Embedded Zipcode zipcode; // 임베디드 타입 포함
//...
}
@Embeddable
public class Zipcode{
String zip;
String plusFour;
}
@Embeddable
public class PhoneNumber {
@ManyToOne PhoneServiceProvider provider; // 엔티티 참조
}
@Entity
public class PhoneServiceProvider {
@Id String name;
//...
}
|
cs |
테이블에 매핑하는 컬럼명이 중복 되면 어떻게 해야할까?
1
2
3
4
|
@Entoty public Member {
@Embedded Address homeAddress;
@Embedded Address companyAddress;
}
|
cs |
이런 경우, @AttributeOverrides를 사용해서 매핑정보를 재정의 하면 된다.
1
2
3
4
5
6
7
8
9
10
11
|
@Entity
public class Member {
@Embedded
@AttributeOverrieds({
@AttributeOverride(name = "city", column = @column(name = "COMPANY_CITY")),
@AttributeOverride(name = "street", column = @column(name = "COMPANY_STREET")),
@AttributeOverride(name = "zipcode", column = @column(name = "COMPANY_ZIPCODE"))
})
Address companyAddress;
}
|
cs |
1
2
3
4
5
6
7
8
9
|
CREATE TABLE MEMBER {
COMPANY_CITY varchar(255),
COMPANY_STREET varchar(255),
COMPANY_ZIPCODE varchar(255),
city varchar(255),
street varchar(255),
zipcode varchar(255),
...
}
|
cs |
해당 어노테이션을 사용하면 이렇게 테이블에 매핑되어 설정된다.
✔️ 임베디드 타입이 Null이면 매핑한 컬럼 값은 모두 null이 된다.
임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험하다.
인스턴스를 공유하면 다른곳에서 변경해도 영향을 받는다. 예상치 못한 곳에서 문제가 발생하게 된다.
값 타입 복사
이런 경우, 값(인스턴스)를 복사해서 사용해야 한다.
1
2
3
4
5
6
7
8
|
member1.setHomeAddress(new Address("Oldcity"));
Address address = member1.getHomeAddress();
//회원1의 address 값을 복사해서 새로운 newAddress 값을 생성
Address newAddress = address.clone();
newAddress.setCity("NewCity");
member2.setHomeAddress(newAddress);
|
cs |
이런식으로 항상 값을 복사해서 넘기면 부작용이 발생할 수 있다.
하지만 객체의 공유 참조는 피할 수 없다. 그래서 수정자 메소드를 모두 제거하면 부작용의 발생을 막을 수 있다.
불변 객체
불변 객체는 값은 조회할 수 있지만 수정할 수 없다.
1
2
3
4
5
6
7
8
9
10
11
12
|
@Embeddable
public class Address {
//생성자로 초기 값을 설정한다.
public Address(String city) {this.city = city;}
//접근자(Getter)는 노출한다.
public String getCity(){
return city;
}
//수정자(Setter)는 만들지 않는다.
}
|
cs |
값 타입의 비교
- 동일성 : 참조 값을 비교, == 사용
- 동등성 : 인스턴스의 값을 비교, equals() 사용
값 타입을 비교할 때는 a.equals(b)를 사용해서 동등성 비교를 해야 한다.
값 타입 컬렉션
값 타입을 하나 이상 저장하려면 컬렉션에 보관하고 @ElementCollection, @CollectionTable 어노테이션을 사용하면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@Entity
public class Member {
@ElementCollection
@CollectionTable(name = "FAVORITE_FOODS",
joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name = "FOOD_NAME")
private Set<String> favoriteFoods = new HashSet<String>();
@ElementCollection
@CollectionTable(name = "ADDRESS",
joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name = "FOOD_NAME")
private List<Address> addressHistory = new ArrayList<Address>();
}
@Embeddable
public class Address {
@Column
private String city;
private String street;
private String zipcode;
}
|
cs |
@CollectionTable를 사용해서 추가한 테이블을 매핑해야한다. 그리고 사용되는 컬럼이 하나면 @Column을 사용해서 컬럼명을 지정할 수 있다. 테이블 매핑정보는 @AttributeOverride를 사용해서 재정의할 수 있다.
값 타입 컬렉션 사용
[등록]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Member member = New Member();
//임베디드 값 타입
member.setHomeaddress(new Adress("통영", "해수욕장", "010-01000"));
//기본 값 타입 컬렉션
member.getFavoriteFoods().add("A");
member.getFavoriteFoods().add("B");
member.getFavoriteFoods().add("B");
//임베디드 값 타입 컬렉션
member.getAddressHistory().add(new Address("서울","강남","123-123"));
member.getAddressHistory().add(new Address("서울","강북","123-123"));
em.persist(member);
|
cs |
1
2
3
4
5
6
|
INSERT INTO MEMBER ();
INSERT INTO FAVORITE_FOODS();
INSERT INTO FAVORITE_FOODS();
INSERT INTO FAVORITE_FOODS();
INSERT INTO ADDRESS();
INSERT INTO ADDRESS();
|
cs |
총 6 번의 INSERT SQL을 실행한다.
값 타입 컬렉션은 영속성 전이 + 고아 객체 제거 기능을 필수로 가진다고 볼 수 있다.
그리고 LAZY가 기본이다.
[조회]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// SQL : SELECT IF, CITY, STREET, ZIPCODE FROM MEMBER WHERE ID = 1
Member member = em.find(Member.class, 1L); // 1.member
// 2. member.homeAddress
Address homeAddress = member.getHomeAddress();
// 3. member.favoriteFoods
Set<String> favoriteFoods = member.getFavoriteFoods(); // LAZY
//SQL : SELECT MEMBER_ID, FOOD_NAME FROM FAVORITE_FOODS
// WHERE MEMBER_ID = 1
for(String favoriteFood : favoriteFoods) { }
// 4. member.addressHistory
List<Address> addressHistory = member.getAddressHistory(); //LAZY
//SQL : SELECT MEMBER_ID, CITY, STREET, ZIPCODE FROM ADDRESS WHERE MEMBER_ID = 1
addressHistory.get(0);
|
cs |
지연로딩을 통해 실제 컬렉션을 사용할 때 SELECT문을 사용하게 된다.
[수정]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Member member = em.find(Member.class, 1L);
//1. 임베디드 값 타입 수정
member.setHomeAddress(new Address());
//2. 기본값 타입 컬렉션 수정
Set<String> favoriteFoods = member.getFavoriteFoods();
favoriteFoods.remove("A");
favoriteFoods.add("B");
//3. 임베디드 값 타입 컬렉션 수정
List<Address> addressHistory = member.getAddressHistory();
addressHistory.remove(new Address());
addressHistory.add(new Address())
|
cs |
1. 임베디드 값 타입 수정 : Member 엔티티만 UPDATE된다.
2. 기본값 타입 컬렉션 수정 : 제거하고 추가해야한다. 수정 할 수 없다.
3. 임베디드 값 타입 컬렉션 수정 : 값 타입은 불변해야하므로 삭제하고 등록해야한다. 값 타입은 꼭 equals, hashcode를 구현해야 한다.
실무에서는 값 타입 컬렉션이 매핑된 테이블에 데이터가 많다면 값 타입 컬렉션 대신에 일대다 관계를 고려해야 한다.
일대다 매핑을 하고 영속성 전이 + 고아객체제거 기능을 적용하면 값 타입 컬렉션처럼 사용할 수 있다.
정리
엔티티 타입의 특성 | 값 타입의 특성 |
식별자가 있다. 생명주기가 있다. 공유할 수 있다. |
식별자가 없다. 생명주기를 엔티티에 의존한다. 공유하지 않는 것이 안전하다. |
📌 식별자가 필요하고 지속해서 값을 추적하고 구분하고 변경해야 한다면 그것은 값 타입이 아닌 엔티티다.
'어플리케이션개발 > JPA' 카테고리의 다른 글
JPA 프로그래밍 학습 정리 (7) - 객체지향 쿼리언어 (2) (1) | 2024.02.01 |
---|---|
JPA 프로그래밍 학습 정리 (7) - 객체지향 쿼리언어 (1) (1) | 2024.01.29 |
JPA 프로그래밍 학습 정리 (5) - 프록시와 연관관계 관리 (0) | 2024.01.22 |
JPA 프로그래밍 학습 정리 (4) - 고급 연관관계 매핑 (0) | 2024.01.19 |
JPA 프로그래밍 학습 정리 (3) - 연관관계 매핑 (0) | 2024.01.18 |