Bepoz
파즈의 공부 일기
Bepoz
전체 방문자
오늘
어제
  • 분류 전체보기 (232)
    • 공부 기록들 (85)
      • 우테코 (17)
    • Spring (85)
    • Java (43)
    • Infra (17)
    • 책 정리 (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Bepoz

파즈의 공부 일기

Spring

[JPA] 값 타입에 대해

2020. 11. 1. 16:47

JPA 값 타입에 대해

JPA의 데이터 타입 분류

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

값 타입 분류

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

임베디드 타입

임베디드 타입을 사용하면 여러 필드들을 묶어서 표현할 수 있다.

public class Member{
    @Id @GeneratedValue
    private Long id;
    private LocalDateTime startDate;
    private LocalDateTime endDate;
}
// 이것을
@Embeddable
public class Period {

    private LocalDateTime startDate;
    private LocalDateTime endDate;
}
//Period 클래스로 만들고 @Embeddable 을 붙여준다.
public class Member{
    @Id @GeneratedValue
    private Long id;
    @Embedded
    private Period workPeriod;
}
//그리고 다음과 같이 사용해주면 된다. DB에는 startDate, endDate 이렇게 필드로 들어가게 된다.
//만약 동일한 Period 클래스가 Embedded 된다면 오류가 난다. 필드명이 같기 때문에 이럴 떄에는 속성을 재정의 해주면 된다.
public class Member{
    @Id @GeneratedValue
    private Long id;
    @Embedded
    private Period workPeriod;

    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "startDate", column = @Column(name = "COMPNAY_STARTDATE")),
        @AttributeOverride(name = "endDate", column = @Column(name = "COMPANY_ENDDATE"))
    })
    private Period companyPeriod;
}
//다음과 같이 @AttributeOverrides, @AttributeOverride 를 이용하면 된다.
//임베디드 타입 안에는 엔티티나 임베디드 타입이 들어갈 수도 있다.
//임베디드 타입이 null이면 매핑한 컬럼 값은 모두 null이 된다.

불변 객체

int a = 10;
int b = a; //기본 타입은 값을 복사
b=4;

Address a = new Address("old");
Address b = a; //객체 타입은 참조를 전달
b.setCity("new") //a 값도 바뀌게 됨
  • 객체 타입은 수정할 수 없게 만들면 부작용을 원천 차단할 수 있다.
  • 값 타입은 불변 객체(immutable object)로 설계해야한다.
  • 불변 객체: 생성 시점 이후 절대 값을 변경할 수 없는 객체
  • 생성자로만 값을 설정하고 수정자(setter)를 만들지 않으면 된다.
  • Integer, String은 자바가 제공하는 대표적인 불변객체이다.

값 타입의 비교

값 타입을 비교할 떄에는 동일성 비교인 == 비교와 동등성 비교인 equals() 비교가 있다.

동일성 비교는 인스턴스의 참조 값을 비교하고, 똥등성 비교는 equals()를 사용하여 값을 비교한다.

        Period p = new Period("a","b");
        Period p2 = new Period("a","b");
        System.out.println(p.equals(p2));
//다음의 경우는 true가 나오게 된다. equals 를 반드시 Period 클래스 내에서 오버라이딩 해주어야 한다. hash 와 함께
        Period p = new Period("a","b");
        Period p2 = new Period("a","b");
        System.out.println(p==p2);
//다음의 경우에는 false가 나오게 된다. 참조 값을 비교하였기 때문이다.

값 타입 컬렉션

일반적인 엔티티와의 매핑이 아닌 값 타입을 컬렉션으로 두어서 사용하려고 할 때에는 관계형 데이터베이스의 테이블은 컬럼 안에 컬렉션을 포함할 수 없기 때문에 별도의 테이블을 추가하고 @CollectionTable를 사용해서 추가한 테이블을 매핑해야 한다. 이 때에 pk 값은 하나의 필드가 아니라 해당 테이블의 모든 필드값을 pk로 가진다. 하나의 필드만 pk 로 가지게되면 그것은 곧 엔티티이기 때문이다.

    @ElementCollection
    @CollectionTable(name = "FAVORITE_FOODS", joinColumns =
                @JoinColumn(name = "MEMBER_ID"))
    @Column(name = "FOOD_NAME")
    private Set<String> favoriteFoods = new HashSet<>();

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

@ElementCollection을 통해 값 타입 컬렉션이라는 것을 알려주고, @CollectionTable를 통해 해당 테이블의 이름과 조인되는 컬럼을 입력해준다. String 같은 경우엔 필드가 하나여서 FOOD_NAME 으로 지정해 준 것이다.

            Member member = new Member();
            member.setName("member1");
            member.setWorkPeriod(new Period("a", "b"));

            member.getFavoriteFoods().add("치킨");
            member.getFavoriteFoods().add("피자");
            member.getFavoriteFoods().add("햄버거");

            member.getPeriodHistory().add(new Period("c", "d"));
            member.getPeriodHistory().add(new Period("e", "f"));

            em.persist(member);
            tx.commit();

다음과 같은 코드를 돌렸고 결과는 이렇게 나왔다.

value_type_collection

보면 값타입들을 별도로 persist 하지 않았는데 다 저장된 것을 볼 수 있다.

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

이제 member를 em.find 하게되면

Hibernate: 
    select
        member0_.MEMBER_ID as MEMBER_I1_2_0_,
        member0_.USERNAME as USERNAME2_2_0_,
        member0_.TEAM_ID as TEAM_ID5_2_0_,
        member0_.a as a3_2_0_,
        member0_.b as b4_2_0_ 
    from
        Member member0_ 
    where
        member0_.MEMBER_ID=?

다음과 같이 나오게 되는데, 값 타입 컬렉션은 지연로딩을 한다는 것을 알 수가 있다.

@ElementCollection 의 기본값이 LAZY로 잡혀져있기 때문이다.

값 타입은 수정 시에 새로 아예 넣어야 된다. 그러니깐 무슨 말이냐면,

findMember.getHomeAddress().setCity("newCity");
//이렇게 하지말고
Address a = findMember.getHomeAddress();
findMember.setHomeAddress(new Address("newCity",a.getStreet(),a.getZipcode()));
//다음과 같이 새로운 객체를 넣어줘야 한다는 것이다.

findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");
// 이 경우에는 String 이므로 remove를 통해 삭제하고 add 한다.

findMember.getAddressHistory().remove(new Address("old1", "street", "10000"));
findMember.getAddressHistory().add(new Address("newCity1", "street", "10000"));
//이 경우에는 보통 equals를 통해 찾는다. 따라서, equals와 hashcode를 제대로 구현해주어야 한다.그리고 새 Address를 add를 해준다.

흥미로운 점은 값 타입 컬렉션에서 remove를 하고 add를 할 때에 발생한다.

값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다. 왜냐하면, 값 타입은 식별자라는 개념이 없고 단순한 값들의 모을이므로 값을 변경해버리면 데이터베이스에 저장된 원본 데이터를 찾기가 어렵기 때문이다. 예로들어서 AddressHistory 에 old1,street,10000의 값을 가진 Address가 들어가있는 상황에서 그것을 remove하면 모두 다 삭제하고 나머지 Address들을 다시 insert를 하는 작업이 이루어진다는 것이다. 따라서, 값 타입 컬렉션 대신에 일대다 관계를 고려하는 것이 좋다.


'Spring' 카테고리의 다른 글

[JPA] merge 동작방식과 기타 어노테이션  (0) 2020.11.03
[JPA] API 성능 끌어올리기(N+1 문제 등)  (0) 2020.11.03
[JPA] 영속성 전이(CASCADE)와 고아 객체에 대해  (0) 2020.10.30
[JPA] 즉시로딩과 지연로딩에 대해  (0) 2020.10.30
[JPA] 기본 키 매핑 전략에 대해  (0) 2020.10.30
    'Spring' 카테고리의 다른 글
    • [JPA] merge 동작방식과 기타 어노테이션
    • [JPA] API 성능 끌어올리기(N+1 문제 등)
    • [JPA] 영속성 전이(CASCADE)와 고아 객체에 대해
    • [JPA] 즉시로딩과 지연로딩에 대해
    Bepoz
    Bepoz
    https://github.com/Be-poz/TIL

    티스토리툴바