JPA의 값 타입 (김영한님 자바 ORM 표준 JPA 프로그래밍 필기 내용)

아래 후술할 여러 내용중 임베디드 타입(복합 값 타입)과, 값 타입 컬렉션에 대한 내용이 특히 중요하다고 한다.

값 타입의 분류

  • JPA의 Data Type은 최상위로 볼 때 크게 Entity TypeValue Type으로 두가지로 분류할 수 있다. 각각 살펴보자면,

Entity Type(엔티티 타입)

  • @Entity로 정의하는 객체들 (여러 테이블들과 같이)로써, 데이터가 변해도 식별자(PK 등)로 지속해 추적이 가능하다. (ex: EntityManager.find(엔티티.clsss, 엔티티.식별자()))
  • 예시로 회원 Entity의 신장, 나이 값을 변경해도 이 엔티티의 식별자로 이를 인식할 수 있다.

Value Type(값 타입)

  • int, Integer, String 처럼 단순히 값으로 사용하는 자바 기본타입이나 객체를 말한다.
  • 값 타입은 식별자가 따로 없고 값으로만 존재하므로 JPA에서 얘를 추적할수는 없다.

이제 조금 더 알아보자면, 아래와 같다.


기본값 타입

기본적으로 기본값 타입은 생명주기는 Entity에 의존한다. 예를 들어, 회원이라는 객체를 하나 삭제하면, 그에 딸린 나이, 이름 필드도 같이 삭제가 된다.

  • 자바 기본 타입(int, double 등)
  • Wrapper Class (Integer, Long, Double 등)
  • String
  • 값 타입은 공유를 하면 안된다. 이렇게 생각해보면 쉬운데, 내가 민수라는 회원의 취미를 "밥먹기"라고 변경하려고 한다. 당연히 민수의 취미만 바꿔야지, 옆에 있는 민식이의 취미도 "밥먹기"로 변경시켜버리면 안된다. 이건 말이 어렵지 그냥 우리가 늘 코딩하며 봤던 상황이다. 예를 들어 아래와 같은 상황에서
public class Test{
	public static void main(String[] args){
    		int a = 10;
    		int b = a; //b에 a의 값이 "복사"됨.
    
    		a = 20; // a의 값을 20으로 바꿔도
    		System.out.println("a = " + a); //얘는 20이지만
	    	System.out.println("b = " + b); //얘는 10 그대로 있다. 같이 안바뀐다.
    	}
}
  • 그냥 자바 자체적으로 기본타입을 다른 객체와 공유하지 않는다.
  • 물론 Integer같은 래퍼클래스나, String과 같은 특수클래스는 레퍼런스를 공유가 가능해 값 자체의 공유는 가능한 놈들이지만, 다행히 자바 형아들이 변경까지 허락해주진 않았다.

아래 임베디드 타입컬렉션 값 타입을 사용하기 위해서는 JPA에서 따로 정의를 해주고 사용할 수 있다.

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

예를 들어, x,y 좌표값과 같은 것을을 관리하기 위해 Position이라는 클래스를 정의했다고 하자. 이 Position 자체를 위의 Integer, double, String 과 같이 특정 으로써 사용하고 싶을 때 사용하는것이 바로 임베디드 타입이라고 한다.

  • 임베디드 타입은 새로운 값 타입을 직접 정의할 수 있다.
  • 위에서의 Position 클래스와 같은 예시처럼 주로 기본 값 타입 들을 모아서 만들어주기 때문에 복합 값 타입이라고도 한다.
  • 임베디드 타입도 int, String과 같은 값 타입 이다. Entity가 아니다. 즉, 이 임베디드 타입으로 엔티티를 추적할수가 없고, 그냥 변경하면 끝나는것.

예시로, 만약 아래와 같은 회원 엔티티가 있다고 하자.

여기서 근무 시작일~ 종료일을 근무기간으로 묶고, 도시, 번지, 우편번호 또한 간단히 클래스로 묶어 집 주소 이렇게 묶을 수 있을것이다.
이렇게 묶어주고 나면 아래 사진과 같이 쥰내 간단한 회원 엔티티가 될 수 있을 것이다.

이렇게 몇가지 기본 타입들을 묶어내서 그 자체를 하나의 타입을 쓰는게 임베디드 타입이다.

  • 여기서 알 수 있는것은, 여러 중복되는 속성을 재사용해 코드를 좀 줄일 수 있고, 높은 응집도를 가진다.
  • 또한 묶어낸 Period 타입 자체가 클래스인 만큼, Period.isWork() : return Boolean과 같이 해당 값 타입만 사용하는 의미있는 메소드를 만들 수 있겠다.
  • 임베디드 타입 또한 값타입이므로, 이 타입의 라이프 사이클 또한 엔티티에 의존한다. 예시를 보자.
@Entity
public class Member extends BaseEntity {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USER_NAME")
    private String username;

    private LocalDateTime startDate;
    private LocalDateTime endDate;

    private String city;
    private String street;
    private String zipCode;

}

위와 같은 회원 엔티티에서 startDate, endDate를 묶어 Period로, city, street, zipCode를 묶어 Address라는 클래스로 따로 정의를 했다. 그 후, 아래 두 클래스는 임베디드 타입이다! 라는 명시를 JPA에게 전달해야 하기 때문에 @Embeddable 어노테이션을 붙여주고, 이를 사용하는 Member 클래스에 해당 객체들을 선언하며, 여기엔 나 임베디드 타입 쓸거다! 라는 명시를 위해 @Embedded 를 붙였다.

@Embeddable
public class Period {
    private LocalDateTime startDate;
    private LocalDateTime endDate;
    public Period(){}
    // ... 이하 getter settet 등
}

@Embeddable
public class Address {
    private String city;
    private String street;
    private String zipCode;
    public Address(){}
    // ... 이하 getter settet 등
}

@Entity
public class Member extends BaseEntity {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USER_NAME")
    private String username;

    @Embedded
    private Period period;

    @Embedded
    private Address address;
}
  • 위 두 방식(임베디드 타입으로 쪼개버리냐, 그냥 쌩으로 Member에 다 때려박고 가냐)모두 DB 테이블의 변화는 없다. 그냥 엔티티를 좀 더 OOP 갬성으로다가 만질 수 있게되는 상큼한 것이다.

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

  • 임베디드 타입은 엔티티의 값일 뿐, 뭐 별 다른 큰건 없다. 임베디드 타입을 쓰기 전과 후의 DB 테이블 구조는 동일하다.
  • 대신, 객체와 테이블을 좀 더 세밀하게 매핑하는 것이 가능해져 까리하다.
  • 잘 설계한 ORM 애플리케이션은 매핑한 테이블 수보다 클래스 수가 더 많다고 한다.

컬렉션 값 타입(Collection value type)


값 타입과 불변 객체


값 타입의 비교


값 타입 컬렉션


좋은 웹페이지 즐겨찾기