[JPA] 값 타입

윤경·2021년 10월 19일
0

JPA

목록 보기
11/22
post-thumbnail

[1] 기본값 타입

JPA 데이터 타입 분류

엔티티 타입

  • @Entity로 정의하는 객체
  • 데이터가 변해도 식별자로 지속해서 추적 가능
    EX. 회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식 가능

값 타입

  • int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
  • 식별자가 없고 값만 있어 변경시 추적 불가
    EX. 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체됨

값 타입 분류

- 기본값 타입

  • 자바 기본 타입(int, double)
  • 래퍼 클래서(Integer, Long)
  • String

- 임베디드 타입(embedded type, 복합 값 타입)

- 컬렉션 값 타입(collection value type)

기본값 타입

: String name, ing age...

생명주기를 엔티티에 의존한다는 중요한 특징이 있다.
(예를 들어 회원을 삭제하면 이름, 나이 필드도 함께 삭제된다.)

값 타입은 공유하면 안된다.
(예를 들어 회원 이름 변경시 다른 회원의 이름도 변경 되어버리면 안되기 때문이다.)
int, double 같이 기본 타입(primitive type)은 절대 공유하면 안된다.
기본 타입은 항상 값을 복사한다.
Integer와 같이 래퍼 클래스나 String같은 특수한 클래스는 공유가 가능한 객체이지만 변경 불가능


[2] 임베디드 타입

: 복합 값 타입, 내장 타입이라고도 함

  • 새로운 값 타입을 직접 정의할 수 있음
  • JPA는 임베디드 embedded type이라 함
  • 주로 기본 값 타입을 모아 만들어 복합 값 타입이라고도 함
  • int, String과 같은 값 타입(추적도 변경도 안됨)

요구사항

  • 회원 엔티티는 이름, 근무 시작일, 근무 종료일, 주소 도시, 주소 번지, 주소 우편번호를 가짐
  • 회원 엔티티는 이름, 근무 기간, 집 주소를 가짐

사용법

  • @Embeddable: 값 타입을 정의하는 곳에 표시
  • @Embedded: 값 타입을 사용하는 곳에 표시
  • 기본 생성자 필수

장점

  • 재사용성
  • 높은 응집도
  • Period.isWork()처럼 해당 값 타입만 사용하는 의미 있는 메소드만 뽑아낼 수 있음
  • 임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티티에 생명 주기를 의존

임베디드 타입과 테이블 매핑

  • 임베디드 타입은 엔티티의 값일 뿐이다.
  • ⭐️ 임베디드 타입을 사용하기 전과 후 매핑하는 테이블은 같다
  • 객체와 테이블을 아주 세밀하게(find-grained) 매핑하는 것이 가능하다.
  • 잘 설계한 ORM 애플리케이션은 매핑한 테이블 수보다 클래스의 수가 더 많다.

임베디드 타입과 연관관계

실습

@AttributeOverride

: 속성 재정의

한 엔티티에서 같은 값 타입을 사용하면 컬럼명이 중복 되어버린다.
그럴 때 @AttributeOverrides@AttributeOverride를 사용해 컬러 명 속성을 재정의 한다.

📌 임베디드 타입의 값이 null이면 매핑한 컬럼 값은 모두 null이다.

실습


[3] 값 타입과 불변 객체

값 타입은 복잡한 객체 세상을 조금이라도 단순화하기 위해 만든 개념. 따라서 값 타입은 단순하고 안전하게 다룰 수 있어야 함

값 타입 공유 참조

  • 임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험하다.
  • 부작용(side effect)가 발생한다.
    member2도 변경된

값 타입 복사

  • 값 타입의 실제 인스턴스인 값을 공유하는 것은 위험하다.
  • 대신 값(인스턴스)를 복사해 사용해야 한다.
    1만 변경된

객체 타입의 한계

  • 항상 값을 복사해 사용하면 공유 참조로 인해 발생하는 부작용을 피할 수 있다.
  • 문제는 임베디드 타입처럼 직접 정의한 값 타입은 자바의 기본 타입이 아니라 객체 타입이다.
  • 자바 기본 타입에 값을 대입하면 값을 복사한다.
  • 객체 타입은 참조 값을 직접 대입하는 것을 막을 방법이 없다.

결론은 객체의 공유 참조는 피할 수 없다.

기본 타입 primitive type

int a = 10;
int b = a;	// 기본 타입은 값을 복사하기 때문에 b = 10인 상태
b = 4;
// b만 4로 변경

객체 타입

Address a = new Address("old");
Address b = a;	// 객체 타입은 참조를 전달
b.setCity("new");
// a와 b 가 같은 인스턴스를 가지고 있으므로 둘 다 "new"로 바뀜

불변 객체

  • 객체 타입을 수정할 수 없게 만들면 부작용을 원천적으로 차단할 수 있다.
  • 값 타입은 불변 객체(immutable object)로 설계해야 한다.
  • 불변 객체: 생성 시점 이후 절대 값 변경 불가
  • 생성자로만 값을 설정하고 수정자(Setter)를 만들지 않거나 private로 생성하면 된다.

    📌 Integer, String은 자바가 제공하는 대표적인 불변 객체

불변이라는 작은 제약으로 부작용이라는 큰 재앙을 막자


[4] 값 타입의 비교

값 타입은 인스턴스가 달라도 그 안의 값이 같으면 같은 것으로 봐야한다.

기본 타입일 때

int a = 10; int b = 10;
이라면 a==b 는 true이다.

객체 타입일 때

Address a = new Address("ㅇㅇ시");
Address b = new Address("ㅇㅇ시");
라면 a==b 는 false이다.

  • 동일성(identity) 비교: 인스턴스의 참조 값을 비교, ==사용
  • 동등성(equivalence) 비교: 인스턴스의 값을 비교, equals() 사용
  • 값 타입은 a.equals(b)를 사용해 동등성 비교를 해야한다.
  • 값 타입의 equals() 메소드를 적절하게 재정의(주로 모든 필드 사용)

[5] 값 타입 컬렉션

: 값 타입을 컬렉션에 담아 쓰는 것

  • 값 타입을 하나 이상 저장할 때 사용
  • @ElementCollection, CollectionTable사용해 매핑
  • 데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.
  • 컬렉션을 저장하기 위한 별도의 테이블이 필요하다.

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

제약사항

  • 값 타입은 엔티티와 다르게 식별자 개념이 없다.
  • 값은 변경하면 추적이 어렵다.
  • 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
  • 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어 기본 키를 구성해야 한다. null 입력 X, 중복 저장 X

대안

  • 실무에서는 상황에 따라 값 타입 컬렉션 대신 일대다 관계를 고려한다.
  • 일대다 관계를 위한 엔티티를 만들고, 여기에서 값 타입을 사용한다.
  • 영속성 전이(Cascade) + 고아 객체 제거를 사용해 값 타입 컬렉션처럼 사용 (Ex. AddressEntity)
// JpaMain.java
try {
            Member member = new Member();
            member.setUsername("member1");
            member.setHomeAddress(new Address("homeCity", "street", "100"));

            member.getFavoriteFoods().add("물회");
            member.getFavoriteFoods().add("미역국");
            member.getFavoriteFoods().add("계란");

            member.getAddressHistory().add(new Address("old1", "street", "100"));
            member.getAddressHistory().add(new Address("old2", "street", "100"));

            em.persist(member);

            em.flush();
            em.clear();

            System.out.println("====== START ======");
            Member findMember = em.find(Member.class, member.getId());

            // homeCity -> newCity
            Address a = findMember.getHomeAddress();
            findMember.setHomeAddress(new Address("newCity", a.getStreet(), a.getZipcode()));

            // 물회 -> 매운탕
            findMember.getFavoriteFoods().remove("물회");
            findMember.getFavoriteFoods().add("매운탕");

            findMember.getAddressHistory().remove(new Address("old1", "street", "100"));
            findMember.getAddressHistory().add(new Address("newCity1", "street", "100"));


            tx.commit();

매운탕

즉,

엔티티 타입 특징

  • 식별자 O
  • 생명 주기 관리
  • 공유

값 타입 특징

  • 식별자 X
  • 생명 주기를 엔티티에 의존
  • 공유하지 않는 것이 안전하지만 어쩔 수 없이 공유해야 할 땐 복사해서 사용하기
  • 불변 객체로 만드는 것이 안전

값 타입은 정말 값 타입이라고 판단될 때만 사용해야 한다.
엔티티와 값 타입을 혼동해 엔티티를 값 타입으로 만들면 안된다.
식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면 그것은 값 타입이 아닌 엔티티이다.


[6] 실전 예제 - 6. 값 타입 매핑

✔️ 실전 예제는 jpashop 프로젝트, jpashop DB를 사용합니다.

📌 참고

체크박스 체크할 것

Address.java를 생성해 객체지향적으로 만들었다.
자세한 코드는 깃허브를 참고


profile
개발 바보 이사 중

0개의 댓글