jpa의 데이터 타입은 다음과 같이 분류할 수 있다.
- 엔티티 타입
- 값 타입
- 기본 값 타입
- 자바 기본 타입 (int, double)
- 래퍼 클래스 (Integer, Long)
- String
- 임베디드 타입
- 컬렉션 값 타입
- 기본 값 타입
엔티티 타입의 경우, @Entity로 정의하는 객체이다.
데이터가 변하더라도 식별자로 지속해서 추적이 가능하다는 특징을 가지고 있다.
값 타입은 int, Integer, String처럼 값으로 사용하는 자바 기본 타입이나 객체를 의미한다.
엔티티 타입과 다르게 식별자가 없고 값만 있어서 변경되면 추적이 불가하다.
기본 값 타입
자바 기본 타입은 항상 값을 복사하는 반면, Integer 같은 래퍼 클래스나 String 같은 특수한 클래스는 공유 가능한 객체이지만 변경은 불가능하다.
임베디드 타입
임베디드 타입의 경우 새로운 값 타입을 직접 정의할 수 있다.
주로 기본 값 타입들을 모아서 만든다.
다음 회원 엔티티가 있다고 하자.
![](https://blog.kakaocdn.net/dn/7Wh67/btsrrag4UPS/pAtZQ5U33SIJZdCvuzVVF1/img.png)
위 엔티티는 임베디드 타입을 활용하면 보다 깔끔하게 관리할 수 있다.
startDate, endDate 필드를 Period 임베디드 타입에서 관리하고, city, street, zipcode를 Address 임베디드 타입에서 관리하도록 수정하였다.
![](https://blog.kakaocdn.net/dn/IXnZm/btsrgoVxY2a/LAK44LYgPpt3ce7c1Iu42K/img.png)
![](https://blog.kakaocdn.net/dn/bRBZ2y/btsrrclEkEC/HuWhKD1gfknV2dTJkpmZF1/img.png)
임베디드 타입을 사용하면 재사용도 가능하고, 높은 응집도를 가지게 된다.
그리고 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 동일하긴 하지만, 객체를 조금 더 객체 지향스럽고, 깔끔하게 관리할 수 있는 것이다.
@Embeddable 어노테이션은 값 타입을 정의하는 곳에, @Embedded는 값 타입을 사용하는 곳에 표시하면 임베디드 타입을 사용할 수 있다.
그리고, 한 엔티티에서 같은 값 타입을 사용할 때는 @AttributeOverride라는 어노테이션을 사용해서 속성을 재정의해주어야 한다.
이는 컬럼 명이 중복되기 때문이다.
다음과 같이 나타내면 된다.
@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;
값 타입과 불변 객체
임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험하다.
한 엔티티에서 값을 변경하면 다른 엔티티에서도 값이 변경되는 부작용이 발생할 수 있기 때문이다.
그래서 값 타입의 실제 인스턴스인 값을 공유하지 말고, 값을 복사해서 사용하도록 하자.
Address address = new Address("city", "street", "10000");
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(address);
em.persist(member);
Address copyAddress = new Address(address.getCity(), address.getStreet(), address.getZipcode());
Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(copyAddress);
em.persist(member2);
member.getHomeAddress().setCity("newCity");
tx.commit();
![](https://blog.kakaocdn.net/dn/bBni51/btsrjqd1ryw/rKQM1Wk0AQttzieXiYgcqK/img.png)
임베디드 타입처럼 직접 정의한 값 타입은 객체 타입이기 때문에, 참조 값을 직접 대입하는 것을 막을 방법이 없다.
그래서, 객체 타입을 수정할 수 없게 만들면 된다.
값 타입은 불변 객체로 설계해서, 생성자로만 값을 설정하고 수정자를 만들지 않거나 private으로 선언하면 된다는 것이다.
값 타입의 비교
- 동일성 비교: 인스턴스의 참조 값 비교, == 사용
- 동등성 비교: 인스턴스의 값을 비교, equals() 사용
값 타입은 equals()를 사용하는 동등성 비교를 해야 한다.
또한, 이 메서드를 적절하게 재정의해서 사용해야 한다.
equals() 재정의 시 그에 맞는 hashCode도 같이 작성해주어야 한다.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Address address = (Address) o;
return Objects.equals(city, address.city) &&
Objects.equals(street, address.street) &&
Objects.equals(zipcode, address.zipcode);
}
@Override
public int hashCode() {
return Objects.hash(city, street, zipcode);
}
값 타입 컬렉션
![](https://blog.kakaocdn.net/dn/GO1Ya/btsr5JJ4hp8/CZktP4xXJf1GMuVHaWhOl1/img.png)
값 타입 컬렉션은 값 타입을 컬렉션에 담아서 쓰는 것이다.
하지만, 관계형 DB에 컬렉션을 테이블에 담을 수 없다.
그래서, 일대다 개념이라 별도의 테이블로 구현해야 한다.
추가적으로, 값 타입 컬렉션은 다음과 같은 특징을 가지고 있다.
- 값 타입 컬렉션은 영속성 전이 + 고아 객체 제거 기능을 필수로 가진다.
- 엔티티와 다르게 식별자 개념이 없다.
- 값을 변경하면 추적이 어렵다.
- 값 타입 컬렉션에 변경 사항이 발생하면 주인 엔티티와 연관된 모든 데이터를 삭제하고 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
- 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야 한다. => null 입력 X, 중복 저장 X
그래서 결론은 값 타입 컬렉션보다는 일대다 관계를 이용하는 것이 좋다.
실전 예제
![](https://blog.kakaocdn.net/dn/pZrc7/btsrYzPKyT1/wVteTZyCCCfadxwbjEW620/img.png)
이제 주소 부분을 Address로 만들고 적용해보자.
package jpabook.jpashop.domain;
import javax.persistence.Embeddable;
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
public String getCity() {
return city;
}
public String getStreet() {
return street;
}
public String getZipcode() {
return zipcode;
}
private void setCity(String city) {
this.city = city;
}
private void setStreet(String street) {
this.street = street;
}
private void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Address address = (Address) o;
return Objects.equals(getCity(), address.getCity()) &&
Objects.equals(getStreet(), address.getStreet()) &&
Objects.equals(getZipcode(), address.getZipcode());
}
@Override
public int hashCode() {
return Objects.hash(getCity(), getStreet(), getZipcode());
}
}
'Backend > Spring' 카테고리의 다른 글
프록시와 연관 관계 관리 (0) | 2023.08.16 |
---|---|
고급 매핑 (0) | 2023.08.16 |
다양한 연관 관계 매핑 (0) | 2023.08.16 |
엔티티 매핑과 연관 관계 매핑 (3) | 2023.08.09 |
영속성 컨텍스트 (0) | 2023.08.09 |