값 타입

OneTwoThree·2023년 7월 31일
0

기본값 타입

JPA에서는 엔티티 타입과 값 타입으로 데이터 타입을 분류한다.

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

값 타입

값 타입에는 기본값 타입, 임베디드 타입, 컬렉션 값 타입이 있다.

  • 기본값 타입
    • int, double 자바 기본 타입
    • Integer, Long 래퍼 클래스
    • String
  • 임베디드 타입(복합 값 타입)
  • 컬렉션 값 타입

기본 값 타입

  • 생명 주기를 엔티티에 의존
  • 값 타입은 공유하면 안된다
    • 부수 효과 (내 이름 변경했는데 다른 사람 이름도 변경됨)

임베디드 타입(복합 값 타입)

새로운 값 타입을 직접 정의할 수 있다. 주로 기본 값 타입을 모아서 만들기 때문에 복합 값 타입이라고도 한다. 엔티티가 아닌 값 타입이다.

  • 근무 시작일 + 근무 종료일
  • 도시 + 번지 + 우편번호

클래스를 새로 뽑는 것과 같다. @Embeddable은 값 타입을 정의하는 곳에 표시하고 @Embedded는 값 타입을 사용하는 곳에 표시한다.
기본 생성자가 있어야 한다.

@Embeddable
public class Period {
    private LocalDateTime startDate;
    private LocalDateTime endDate;
    
    
}

값을 정의하는 곳에는 @Embeddable

    @Embedded
    private Period workPeriod;

값을 사용하는 곳에는 @Embedded

  • 두 애노테이션 중에 하나만 넣고 생략해도 되지만 둘다 넣는 것이 직관적이고 좋다

임베디드 타입을 사용하기 전과 후 매핑하는 테이블은 같다
객체와 테이블을 아주 세밀하게 매핑하는 것이 가능하다

임베디드 타입이 임베디드 타입을 필드로 가질 수 있다
임베디드 타입이 엔티티를 필드로 가질 수 있다


한 엔티티에 주소가 두 개 있다면?
@AttributeOverides, @AttributeOveride를 사용해서 컬럼 명 속성을 재정의 할 수 있다.

임베디드 타입 값 자체가 null이면 해당 값들은 모두 null이 된다.

값 타입과 불변 객체

임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 side effect가 발생할 수 있다.
같이 공유하고 싶으면 값 타입이 아닌 엔티티를 사용하는게 맞다

임베디드 타입을 사용할 경우 기존 임베디드 타입 값을 복사해서 새로 객체를 만들어서 사용하는 게 맞다.

기본 타입은 int b=a 하면 값을 복사함
객체 타입은 Address b=a 하면 참조를 전달함

해결 방법 : 값 타입을 불변 객체로 만든다

  • 생성 시점 이후 값을 변경할 수 없는 객체
  • 생성자로만 값을 설정하고 수정자를 만들지 않는다
  • Integer, String등이 대표적인 불변 객체이다

값 타입의 비교

값 타입은 인스턴스가 달라도 그 안의 값이 같으면 같은 것으로 봐야 한다.
자바에서는 == 비교가 참조값을 비교하므로 값이 같은 다른 객체를 생성하면 다른 것으로 비교함
동일성 비교와 동등성 비교를 구분해서 사용해야 한다.

  • 동일성 비교
    • == 비교. 인스턴스의 참조 값을 비교
  • 동등성 비교
    • equals() 사용. 인스턴스의 값을 비교
    • 기본적으로 equals()== 비교를 하기 때문에 오버라이딩을 해줘야 한다.
    • 값 타입의 비교는 항상 equals()를 오버라이딩 해서 사용하자

값 타입 컬렉션

값 타입을 컬렉션에 담아서 사용하는 것이다.
관계형 데이터베이스는 컬렉션을 DB에 넣을 수가 없다.

이런 식으로 일대 다 관계로 별도의 테이블을 뽑아야 한다.
즉 컬렉션을 저장하기 위한 별도의 테이블이 필요하다.

    @ElementCollection
    @CollectionTable(name = "FAVIROTE_FOOD",
            joinColumns = @JoinColumn(name="MEMBER_ID")
    )
    private Set<String> favoriteFood = new HashSet<>();

    @ElementCollection
    @CollectionTable(name="PERIODS",
            joinColumns = @JoinColumn(name="MEMBER_ID")
    )
    private List<Period> periods = new ArrayList<>();
 

값 타입 컬렉션인 경우 @ElementCollection을 붙여줘야 함
@CollectionTable로 테이블 명을 지정해 줘야 함

joinColumns로 매핑해줘야 한다.

값 타입은 별도로 업데이트 할 필요 없이 Member의 값을 업데이트 하면 알아서 반영된다.

컬렉션들은 다 지연 로딩으로 설정되어있다.

값 타입은 immutable 해야 하기 때문에 setter를 사용하지 않는다.
새로운 값 타입 객체를 만들어서 새로 넣는게 맞다.

member.getHomeAddress().setCity("newCity"); // setter 사용 X!
member.setHomeAddress(new Address("newCity",..,..)); // 새롭게 값 타입을 넣어줘야 한다

값 타입 컬렉션 업데이트는 아래와 같이 한다

findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");

findMember.getAddressHistory().remove(new Address("oldCity"));
findMember.getAddressHistory().add(new Address("newCity"));

컬렉션의 경우 remove로 삭제할 때 equals로 비교해서 찾기 때문에 equals의 해시코드를 제대로 구현해 놓아야 한다.

해시코드 관련


값 타입 컬렉션에 변경 사항이 발생하면 주인 엔티티와 관련된 모든 데이터를 삭제하고, 값 타입 컬렉션에 현재 있는 값을 모두 다시 저장한다.

값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성한다.
(null X, 중복 저장 X)


실무에서는 값 타입 컬렉션 대신 일대다 관계를 고려하는게 좋다
일대다 관계를 위한 엔티티를 만들고, 엔티티에서 값 타입을 사용한다.
영속성 전이(cascade all) + 고아 객체를 사용해서 값 타입 컬렉션처럼 사용하는게 좋다.

    @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
    @JoinColumn(name="member_id")
    private List<Period> period;

진짜 단순한 경우가 아니면 값 타입 컬렉션을 사용하지 않음

지속해서 값을 추적, 변경해야 한다면 그것은 값 타입이 아닌 엔티티이다.


0개의 댓글