이 글은 김영한 님의 저서 「자바 ORM 표준 JPA 프로그래밍」을 학습한 내용을 정리한 글입니다. 모든 출처는 해당 저서에 있습니다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
//값 타입
private String name;
private int age;
}
필드 | 식별자 | 생명주기 |
---|---|---|
id | O | O |
name | x | 엔티티에 의존 |
age | x | 엔티티에 의존 |
→ 엔티티 인스턴스 제거 시 name
, age
도 같이 제거 됨
💡 참고
래퍼 클래스나
String
타입은 객체지만 자바언어에서 기본 타입처럼 사용할 수 있게 지원하므로 기본값 타입으로 분류
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
//근무 기간
@Temporal(TemporalType.DATE)
java.util.Date startDate;
@Temporal(TemporalType.DATE)
java.util.Date endDate;
//집 주소 표현
private String city;
private String street;
private String zipcode;
...
}
📕 컴포지션(Composition)
다른 객체의 인스턴스를 자신의 인스턴스 변수로 포함해서 메소드를 호출하는 기법
/*
** 회원 엔티티
*/
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
//근무 기간
@Embedded Period workPeriod; //근무 기간
@Embedded Address homAddress; //집 주소
...
}
/*
** 임베디드 타입 - 기간
*/
@Embeddable
public class Period {
@Temporal(TemporalType.DATE)
java.util.Date startDate;
java.util.Date endDate;
//..
public boolean isWork(Date date) {
//.. 값 타입을 위한 메소드 정의
}
}
/*
** 임베디드 타입 - 주소
*/
@Embeddable
public class Address {
@Column(name="city") //매핑할 컬럼 정의 가능
private String city;
private String street;
private String zipcode;
//..
}
@Embeddable
: 값 타입을 정의하는 곳에 표시@Embedded
: 값 타입을 사용하는 곳에 표시💡 참고
하이버네이트는 임베디드 타입을 컴포넌트(component)라고 함
*컴포넌트 → 하나의 컴포넌트는 엔티티 참조가 아닌 value 타입으로서 영속화 되는 하나의 포함된 객체를 의미
@Entity
public class Member {
@Embedded
Address address; //임베디드 타입 포함
@Embedded
PhoneNumber phoneNumber; //임베디드 타입 포함
//...
}
@Embeddable
public class Address {
String street;
String city;
String state;
@Embedded
Zipcode zipcode; //임베디드 타입 포함
}
@Embeddable
public class PhoneNumber {
String areaCode;
String localNumber;
@ManyToOne
phoneServiceProvider provider; //엔티티 참조
}
@Entity
public class PhonServiceProvider {
@Id
String name;
...
}
@AttributeOverride
: 속성 재정의@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
// 테이블에 매핑하는 컬럼명이 중복됨
@Embedded
Address homeAddress;
@Embedded
Address companyAddress;
}
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded
Address homeAddress;
@Embedded
@AttributeOverride({
@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;
}
null
이면 매핑한 컬럼 값은 모두 null
member1.setHomeAddress(new Address("OldCity"));
Address address = member1.getHomeAddress();
address.setCity("NewCity"); //회원1의 address 값을 공유해서 사용
member2.setHomeAddress(address);
city
속성이 변경된 것으로 판단💡 부작용(side effect)
- 무언가를 수정했는데 전혀 예상치 못한 곳에서 문제가 발생하는 것
- 해결방법 → 값을 복사해서 사용
member1.setHomeAddress(new Address("OldCity"));
Address address = member1.getHomeAddress();
//회원1의 address 값을 복사해서 새로운 newAddress 값을 생성
Address newAddress = address.clone();
newAddress.setCity("NewCity");
member2.setHomeaddress(newAddress);
영속성 컨텍스트가 회원2의 주소만 변경된 것으로 판단
→ 회원2에 대해서만 UPDATE SQL 실행
항상 값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용을 방지할 수 있음
직접 정의한 값 타입 = 객체 타입
문제점
해결 방법
한 번 만들면 절대 변경할 수 없는 객체
/*
** 주소 불변 객체
*/
@Embbeddable
public class Address {
private String city;
protected Address() {} //JPA에서 기본 생성자는 필수
//생성자로 초기 값 설정
public Address(String city) {
this.city = city;
}
//접근자(Getter) 노출
public string getCity() {
return city;
}
//수정자 생성 x
}
/*
** 불변 객체 사용
*/
Address address = member1.getHomeAddress();
//회원1의 주소값을 조회해서 새로운 주소값을 생성
Address newAddress = new Address(address.getCity());
member2.setHomeAddress(newAddress);
자바가 제공하는 객체 비교
비교 | 대상 | 사용 |
---|---|---|
동일성 (Identity) | 인스턴스의 참조 값 | == |
동등성 (Equivalence) | 인스턴스의 값 | equals() |
값 타입 → a.equals(b)
를 사용한 동등성 비교
값 타입의 equals()
메소드 재정의 필요
→ 모든 필드의 값을 비교하도록 구현
💡 참고
- 자바에서
equals()
를 재정의할 때hashCode()
도 같이 재정의하는 것이 안전함
→ 해시를 사용하는 컬렉션(HashSet
,HashMap
)의 정상 작동을 위함- 자바 IDE에는 대부분
equals
,hashCode
메소드를 자동으로 생성해주는 기능 존재
값 타입을 하나 이상 저장할 경우 컬렉션에 보관
단순한 자료를 저장하거나 추적이 필요 없을 경우 사용
@ElementCollection
cascade
옵션 제공 x@CollectionTable
Member
엔티티의 addressHistory
→ Member_addressHistory
@AttributeOverride
를 사용하여 재정의 가능@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<String>();
@ElementCollection
@CollectionTable(
name = "ADDRESS",
joinColumns = @JoinColumn(name = "MEMBER_ID")
)
private List<Address> addressHistory = new ArrayList<Address>();
//...
}
@Embeddable
public class Address {
@Column
private String city;
private String street;
private String zipcode;
//...
}
Member member = new Member();
//임베디드 값 타입
member.setHomeAddress(new Address("통영", "몽돌해수욕장", "660-123");
//기본값 타입 컬렉션
member.getFavoriteFoods().add("짬뽕");
member.getFavoriteFoods().add("짜장");
member.getFavoriteFoods().add("탕수육");
//임베디드 값 타입 컬렉션
member.getAddressHistory().add(new Address("서울", "강남", "123-123");
member.getAddressHistory().add(new Address("서울", "강북", "000-000");
em.persist(member);
INSERT INTO MEMBER (ID, CITY, STREET, ZIPCODE) VALUES (1, '통영', '몽돌해수욕장', '660-123')
INSERT INTO FAVORITE_FOODS (MEMBER_ID, FOOD_NAME) VALUES (1, "짬뽕")
INSERT INTO FAVORITE_FOODS (MEMBER_ID, FOOD_NAME) VALUES (1, "짜장")
INSERT INTO FAVORITE_FOODS (MEMBER_ID, FOOD_NAME) VALUES (1, "탕수육")
INSERT INTO ADDRESS (MEMBER_ID, CITY, STREET, ZIPCODE) VALUES (1, '서울', '강남, '123-123')
INSERT INTO ADDRESS (MEMBER_ID, CITY, STREET, ZIPCODE) VALUES (1, '서울', '강북', '000-000')
member
: 1번member.homeAddress
member.favoriteFoods
: 3번member.addressHistory
: 2번💡 참고
값 타입 컬렉션은 영속성 전이(
cascade
) + 고아 객체 제거(ORPHAN REMOVE
) 기능을 필수로 가진다고 볼 수 있음
LAZY
가 기본@ElementCollection(fetch = FetchType.LAZY)
//SQL: SELECT ID, CITY, STREET, ZIPCODE FROM MEMBER WHERE ID = 1
Member member = em.find(Member.class, 1L); //1. member
//2. member.homeAddress
Address homeAddress = member.getHomeAddress(); //LAZY
//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) {
System.out.println("favoriteFood = " + favoriteFood);
}
//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);
member
: 1번homeAddress
도 함께 조회member.favoriteFoods
: LAZY로 설정, 실제 컬렉션 사용시 1번 호출member.addressHistory
: 상동Member member = em.find(Member.class, 1L);
//임베디드 값 타입 수정
member.setHomeAddress(new Address("새로운도시", "신도시1", "123456");
//기본값 타입 컬렉션 수정
Set<String> favoriteFoods = member.getFavoriteFoods();
favoriteFoods.remove("탕수육");
favoriteFoods.add("치킨");
//임베디드 값 타입 컬렉션 수정
List<Address> addressHistory = member.getAddressHistory();
addressHistory.remove(new Address("서울", "기존 주소", "123-123"));
addressHistory.add(new Address("새로운도시", "새로운 주소", "123-456");
임베디드 값 타입 수정
MEMBER
테이블과 매핑했으므로 MEMBER
테이블만 UPDATEMember
엔티티를 수정하는 것과 동일기본값 타입 컬렉션 수정
String
타입은 수정 불가능임베디드 값 타입 컬렉션 수정
equals
, hashcode
구현 필수문제점
값 타입은 식별자라는 개념이 존재하지 않으며 단순한 값들의 모음
→ 값 변경시 데이터베이스에 저장된 원본 데이터 조회 어려움
해결 방안
값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어 기본 키를 구성
→ 데이터베이스 기본 키 제약 조건으로 인해 제약 존재
- 컬럼에
null
입력 불가능- 같은 값 중복 저장 불가능
실무) 값 타입 컬렉션이 매핑된 테이블에 데이터가 많을 경우
Cascade
) + 고아 객체 제거(ORPHAN REMOVE
) 기능 적용@Entity
public class AddressEntity {
@Id
@GeneratedValue
private Long id;
@Embedded
Address address;
...
}
@Entity
public class Member {
...
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "MEMBER_ID")
private List<AddressEntity> addressHistory = new ArrayList
}
💡 참고
값 타입 컬렉션 변경할 때 JPA 구현체들이 테이블의 기본 키를 식별해서 변경된 내용만 반영하려고 하나, 사용하는 컬렉션이나 여러 조건에 따라 기본 키 식별 여부가 달라지므로 모두 삭제하고 다시 저장하는 최악의 시나리오를 고려하며 사용해야 한다.
구분 | 엔티티 타입 | 값 타입 |
---|---|---|
식별자 | 존재(@Id )- 식별자로 구분 가능 | X |
생명 주기 | 존재(생성, 영속화, 소멸) - 영속화 : em.persist(entity) - 소멸 : em.remove(entity) | 엔티티에 의존 - 의존하는 엔티티 제거시 같이 제거 |
공유 | 참조 값 공유 가능(공유 참조) ex) 회원 엔티티 존재 → 다른 엔티티에서 자유롭게 참조 가능 | 공유하지 않는 것이 안전 - 값을 복사해서 사용해야 함 - 오직 하나의 주인만이 관리해야 함 - 불변 객체로 만드는 것이 안전 |
📖 참고