[JPA] 값 타입

김용현·2023년 12월 24일
0

JPA

목록 보기
8/12

본 포스트는 김영한 님의 자바 ORM 표준 JPA 프로그래밍 강의를 토대로 작성하였습니다.

JPA의 타입 분류

JPA에서는 크게 두 가지 타입으로 나뉜다.

  • 엔티티 타입
    • 데이터가 변해도 식별자로 추적 가능
    • @Entity로 정의하는 객체
  • 값 타입
    • 값만 있으므로 변경 시 추적 불가
    • int, Integer, String 처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체 타입

엔티티 타입

엔티티 타입은 쉽게 말해 @Entity가 붙은 클래스로 만든 객체를 말한다. 즉 DB와 매핑되는 클래스들을 말한다.

값 타입

값 타입은 다시 3가지로 분류될 수 있는데,

  • 기본값 타입
  • 임베디드 타입
  • 컬렉션 값 타입

다음으로 나뉠 수 있다. 이러한 값 타입들은 엔티티 내에서 하나의 필드들로 활용되는데, 하나씩 알아보자.

기본값 타입

자바에 존재하는 기본적인 타입들이다. primitive type과 래퍼 클래스, String 등의 보통 사용하는 타입들을 말한다.

//Member Entity

class Member{
	String name;
    int age;
}

위 코드처럼 Member 라는 엔티티의 필드들을 구성하는 타입 중 기본 타입에 해당하는 것들을 기본값 타입이라고 한다.

임베디드 타입

사용자가 직접 정의한 타입을 말한다. (ex Period 클래스, Address 클래스 등) @Entity가 붙지 않은 사용자가 만든 클래스들이 해당한다.

예를 들어 Member Entity가 다음과 같은 필드들을 가진다고 해보자.

//Member Entity

class Member{
	@Id @GeneratedValue
    Long id;
	String name;
    LocalDateTime startDate;
    LocalDateTime endDate;
    String city;
    String street;
    String zipcode;
}

이 필드들 중 LocalDateTime 타입들은 Period 라는 클래스로 분리하고, city, street, zipcode 필드들은 Address 라는 클래스로 분리할 수 있다. 따라서

//Member Entity
class Member{
	@Id @GeneratedValue
    Long id;
	String name;
    @Embedded
	Period period; 
    @Embedded
    Address address;
}

//Period Class
@Embeddable
class Period{
	LocalDateTime startDate;
    LocalDateTime endDate;
}

//Address Class
@Embeddable
class Address{
    String city;
    String street;
    String zipcode;
}

다음과 같이 나눌 수 있다. 그러나 Address, Period는 @Entity가 붙어있지는 않으므로 엔티티 객체는 아니지만 Member 엔티티에 소속되어 사용할 수 있다.

실행 결과를 보면 다음과 같이 각각 클래스를 분리했지만 Member table에는 전부 들어와 있는 것을 알 수 있다. 이처럼 임베디드 타입을 사용한다는 것은 테이블 상 나누는 것이 아닌, 객체 관점에서만 분리되는 것임을 알 수 있다.

사용 방법

@Embeddable
임베디드 타입 클래스에 붙이면 된다. (위 예시 코드 참조)

@Embedded
임베디드 타입 필드에 붙이면 된다. (위 예시 코드 참조)

❗️ 참고

  • 임베디드 타입 클래스는 반드시 기본 생성자가 존재해야 한다.
  • 한 엔티티 내에서 같은 값 타입을 사용할 시 컬럼명이 중복되게 된다.
    따라서 @AttributeOverrides, @AttributeOverride를 이용해 컬럼 명 속성을 재정의 해야 한다.
  • 임베디드 타입의 값이 nulldlaus 매핑한 컬럼 값은 모두 null 이다.

❗️ 주의

다음처럼 임베디드 타입의 경우 객체가 전달되기 때문에 잘못하면 하나만 바꾸고 싶은데 다른 컬럼 값도 바뀔 수 있다. 따라서 애초에 set 함수를 없애 변경 자체를 막거나 꼭 필요한 경우 객체를 복사해서 집어넣어야 한다.

값 타입 비교

자바에서는 == 연산은 원시 타입만 가능하다. 객체 타입의 경우 주소 값이 다르기 때문에 값은 값들을 가지고 있다 하더라도 다르다는 결과가 나온다. 따라서 equals 메소드와 hash 메소드를 재정의해주어야 한다.

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Member member = (Member) o;
        return Objects.equals(getId(), member.getId()) && Objects.equals(getName(),
                member.getName()) && Objects.equals(getWorkPeriod(),
                member.getWorkPeriod()) && Objects.equals(getHomeAddress(), member.getHomeAddress());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getId(), getName(), getWorkPeriod(), getHomeAddress());
    }

컬렉션 값 타입

사용자가 만든 타입들을 컬렉션에 넣어서 사용하는 타입을 말한다. (ex List<Address> 등)

예를 들어

이러한 구조를 만들 때 Member에 필드로 컬렉션 값 타입을 만들어야 한다. 그러나 실제 DB에는 위 그림과 같이 따로 테이블이 만들어져 연관관계를 맺게 된다.

그러나 일반적인 엔티티 간의 연관관계와는 다르게 ID 컬럼을 가지지 않고 모든 컬럼들을 PK로 활용하게 된다.

사용방법

@ElementCollection, @CollectionTable 어노테이션을 사용한다.

//Member Entity 클래스 중 일부
 @ElementCollection
    @CollectionTable(name = "FAVORITE_FOOD",
            joinColumns = @JoinColumn(name = "MEMBER_ID"))
    private Set<String> favoriteFoods = new HashSet<>();

    @ElementCollection
    @CollectionTable(name = "ADDRESS",
            joinColumns = @JoinColumn(name = "MEMBER_ID"))
    private List<Address> addressHistory = new ArrayList<>();

@ElementCollection
해당 필드가 컬렉션 값 타입임을 알리는 어노테이션

@CollectionTable
컬렉션 값 타입을 위한 테이블 정보를 설정하는 어노테이션

❗️참고
컬렉션 값 타입은 영속성 전이(Cascade) + 고아 객체 제거(@OrphanRemoval) 기능을 필수로 가진다고 볼 수 있다.

그러나 값 타입 컬렉션은 식별자가 따로 존재하지 않기 때문에 추적이 쉽지 않다. 또한 변경 발생 시 모든 데이터를 지웠다 다시 저장하기를 반복하여 성능에도 좋지 않다. 따라서 이렇게 힘들게 쓸 바에는 아예 엔티티로 승격시켜서 온전한 테이블과 매핑시켜 사용하도록 하는 것이 좋다.

정리

profile
평생 여행 다니는게 꿈 💭 👊 😁 🏋️‍♀️ 🦦 🔥

0개의 댓글