티스토리 뷰

Spring/JPA

JPA - 값 타입

땅속 디그다 2022. 8. 23. 11:17

🌱 값 타입

JPA 의 데이터 타입은 2가지 이다.

  1. Entity - @Entity 로 영속 대상 객체
    • 식별자를 통해 지속적인 추적이 가능 (dirty check 가능)
    • 엔티티의 속성 값을 변경하더라도 같은 엔티티
  2. 값 타입 - 자바 기본 타입 or 객체
    • 식별자가 없고 숫자나 문자같은 속성만 있으므로 추적 불가
    • 변경시에 side effect를 주의 해야한다.

❓side effect?

래퍼 클래스나 primitive 자료형을 사용했었더라면 신경을 쓸 일이 없었다.

하지만 클래스 객체들의 경우 setter같은 메서드들이 열려 있을 경우

참조로 복사가 일어나기 때문에 한쪽에서 변경을 했을경우 다른쪽에서 변경이 일어나는
부수 효과가 발생 할 수 있다. (래퍼 클래스는 setter 메서드 존재 하지 않아 불가능)

JPA 에서의 값 타입은 3가지로 나뉜다.

  1. 기본 값 타입
    • 자바 기본 타입
    • 래퍼 클래스
    • String
  2. 임베디드 타입(복합 값 타입)
    • 사용자 정의 클래스
  3. 컬렉션 값 타입
    • 하나 이상의 값 타입을 저장할 때...

🚀 임베디드 타입

JPA 에서 새로운 값 타입을 직접 정의해서 사용하는 타입

임베디드 타입도 int, String 처럼 값 타입이다.

Member 엔티티에서 사용자 정의 값타입을 만들어 보자

🖥 EmbeddedType 사용 X

@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;
}

를 변경 해보자

🖥 EmbeddedType 사용

@Entity
public class Member {
  @Id @GeneratedValue
  private Long id;
  private String name;
  @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") // 매핑할 컬럼 정의 가능
   private String city;
   private String street;
   private String zipcode;
   // ..
}

값 타입의 장점?

해당 값 타입만 사용하는 의미 있는 메소드를 만들어서 사용하기 좋음

비지니스 로직에서 유효성 검사 같은거 하기 좋다.

=> 공통로직으로 단순화가 좋아진다.

임베디드 타입을 포함한 모든 값 타입은 엔티티의 생명주기에 의존

@Embeddable, @Embedded

@Embeddable : 값 타입을 정의하는 곳에 표시
@Embedded : 값 타입을 사용하는 곳에 표시

임베디드 타입은 기본 생성자가 필수다

연관관계 + 테이블 매핑

값타입은 엔티티의 값일 뿐이여서 값이 속한 엔티티의 테이블에 매핑한다.

한 테이블에 같이 소속(?) 된다고 말할 수 있다.

임베디드 타입은 값 타입을 포함하거나 엔티티를 참조할 수 있다.

임베디드 안에서 fk를 가질 수 있다는 말

🖥 엔티티를 참조하는 임베디드 타입

@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 Zipcode {
    String zip;
    String plusFour;
}
@Embeddable
public class PhoneNumber {
    String areaCode;
    String localNumber;
    @ManyToOne 
    PhoneServiceProvider provider; // 엔티티 참조
    ...
}
@Entity
public class PhoneServiceProvider {
    @Id String name;
    ...
}

속성 재정의 @AttributeOverride

테이블 매핑 정보를 재정의 한다.

🖥 @AttributeOverride

// 집 주소와 회사 주소를 갖도록 할 경우.
@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String name;
    @Embedded Address homeAddress;
    @Embedded
    @AttributeOverrides({
        @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;
}

🤔 주의 사항

프로젝트를 진행 하던 와중 임베디드 타입을 썼는데

예를 들어 (Address 가 사용자 정의 객체로 되어있다고 가정) Address의 속성이 전부
null 이라고 한다면 객체 정보가 Address address = null 형식으로 되어있다.

만약 Address 내부의 속성이 전부 출력되고 null 이 표기 되기를 원한다면 yml에 다음 속성을 추가하자

spring.jpa.properties.hibernate.create_empty_composites.enabled = true

 

❗ side Effect의 방지

값타입을 안전하게 다룰 수 있기 위한 노력이다.

위에서 말했던 side Effect 를 방지 해보자

값타입을 여러 엔티티에서 공유하고 setter 를 열어 두게 된다면

원하지 않은 Entity에서 dirty checking이 되어버리는 이상한 현상이 나오게 된다.

방법 2가지를 살펴보자

  1. 깊은 복사의 사용
    • 참조값을 복사하는 것이 아닌 값을 복사하여 사용하자
  2. 불변 객체의 사용
    • setter 를 열지 않고 사용해 불변 객체를 만들자

JAVA 관련 사항 (비교)

자바가 제공하는 객체 비교는 2가지

  1. 동일성 비교 (참조값 비교), == 연산자를 사용
  2. 동등성 비교 (값 비교), 인스턴스의 값을 비교, equals() 메서드 사용

값 타입의 동등성을 비교하기 위해서는 equals() 메소드를 재정의할 때 모든 필드 값을 비교하도록 재정의
hashCode() 또한 재정의 하여 동일성 비교 또한 같게 만들자.

값 타입 컬렉션

객체에서의 컬렉션은 항상 테이블이 생성된다.

값 컬렉션은 테이블 컬럼 구조가 다음과 같다

속성들 모두가 PK 값으로 걸린다.
값 타입을 가지고 있는 entity 의 pk 값을 fk 및 pk 로 추가로 적용한다.

 

🖥 값 타입 컬렉션 테이블 생성

@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") // FAVORITE_FOODS 테이블에 값으로 사용되는 컬럼이 FOOD_NAME 하나 뿐이라서 @Column을 사용해 컬럼명을 지정할 수 있다.
    private Set<String> favoriteFoods = new HashSet<String>();

    @ElementCollection
    @CollectionTable(name = "ADDRESS",
      joinColumns = @JoinColumn(name = "MEMBER_ID"))
    private Set<Address> addressHistory = new ArrayList<Address>();
    //...
}
@Embeddable
public class Address {
    @Column
    private String city;
    private String street;
    private String zipcode;
    //...
}

😱 값 타입 컬렉션은 영속성 전이 + 고아 객체 제거 기능을 필수로 가지게 된다.

값 타입 수정하기

  1. 임베디드 값 타입 수정
    임베디드 값 타입은 Entity 테이블과 매핑했으므로 MEMBER 테이블만 UPDATE 한다

  2. 임베디드 값 타입 컬렉션 수정
    값 타입은 불변해야 해서 컬렉션에서 기존 주소를 삭제하고 새로운 주소를 등록한다.
    이때 값 타입은 equals(), hashCode()를 꼭 구현해야 한다.

값 타입 컬렉션의 수정

값타입은 추적이 불가능 하므로 값 타입 컬렉션 테이블을 update 할 수없다(dirty checking 시점에서)
JPA는 추적 불가능한 성격을 가진 값 타입 컬렉션을 수정하기 위해서 해당 엔티티에 속한 모든 값타입을
DB에서 삭제하고 다시 모든 값을 저장한다.

또한 위에서 말했듯이 모든 column들을 PK로 등록하기 때문에 칼럼에 null 을 입력할 수 없다. 중복 또한 허용하지
않는다.

 

=> 고로 값 타입 컬렉션을 사용할 일 이있다면 일대다 매핑을 하되 연관관계의 주인을 일쪽으로
가져 가게 하고 cascade + orphanRemove 특성을 가져가도록 하자.

'Spring > JPA' 카테고리의 다른 글

커넥션 풀  (0) 2022.08.23
JDBC 사용해보기  (0) 2022.08.23
프록시와 연관관계 관리  (0) 2022.08.23
고급 매핑  (0) 2022.08.23
JPA - 연관관계 이해하기  (0) 2022.08.03
댓글
02-02 14:12
Total
Today
Yesterday
링크