공부/JPA

JPA 값 타입 - 임베디드 타입

데부한 2022. 7. 12. 21:30
반응형

출처 : 자바 ORM 표준 JPA 프로그래밍 인프런 강의

 

JPA 게시글은 대부분 인프런의 김영한님의 강의인 '자바 ORM 표준 JPA 프로그래밍' 기반으로 내용을 정리했습니다.

 

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

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

 

예제(회원)

  • 회원은 이름, 근무 시작일, 근무 종료일, 주소 도시, 주소 번지, 주소 우편번호를 가진다.

회원 엔티티를 보면 몇 개의 필드를 하나로 합쳐 공통화 할 수 있는 부분들이 보인다.

  • workPeriod : startDate + endDate
  • homeAddress : city + street + zipcode

뭔가 어려워보이지만 그냥 공통화 할 수 있는 부분을 따로 빼서 클래스화 한 것이다.

 

임베디드 타입 사용법

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

 

임베디드 타입 장점

  • 재사용 가능
  • 높은 응집도
  • Period.isWork()처럼 해당 값 타입만 사용하는 의미있는 메서드를 만들 수 있다. 즉 객체지향적이다.
  • 임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티티에 생명주기를 의존한다.

 

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

DB 입장에서는 임베디드 타입을 사용하던 안하던 회원테이블은 변함없이 똑같다. 임베디드 타입을 사용하면 대신 매핑 작업이 추가로 들 뿐이다. 매핑 작업이 추가로 필요함에도 불구하고 임베디드 타입을 굳이 사용하는 이유는 객체 입장에서는 메서드, 즉 행위까지 생각해야하고 임베디드 타입을 사용하면 이런 부분에서 많은 이득을 가져갈 수 있다.

Member.class

@Entity
public class Member  {

    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

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


    // 기간
    @Embedded
    private Period workPeriod;

    // 주소
    @Embedded
    private Address homeAddress;
}

Period.class

@Embeddable
public class Period {

   private LocalDateTime startDate;
   private LocalDateTime endDate;

    public Period() {
    }
	
    // getter/setter
}

Address.class

@Embeddable
public class Address {
    private String city;
    private String street;
    private String zipcode;

    public Address() {
    }
    
    // getter/setter
}

출력 로그

Hibernate: 
    
    create table Member (
       MEMBER_ID bigint not null,
        city varchar(255),
        street varchar(255),
        zipcode varchar(255),
        USERNAME varchar(255),
        endDate timestamp,
        startDate timestamp,
        primary key (MEMBER_ID)
    )

 

사실 @Embeddable, @Embedded 둘 중 하나만 사용해도 알아서 적용되지만 웬만하면 두 개 다 사용하는 걸 권장한다. Member 객체에 기간과 주소와 관련된 필드를 다 적어도, 임베디드 타입을 사용해도 위 출력 로그가 동일하게 전송된다.

 

실행 클래스

Member member = new Member();
member.setUsername("member");
member.setHomeAddress(new Address("city", "street", "100"));
member.setWorkPeriod(new Period());

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


// 출력 로그
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (city, street, zipcode, USERNAME, endDate, startDate, MEMBER_ID) 
        values
            (?, ?, ?, ?, ?, ?, ?)

Insert 쿼리가 전송되고, DB에도 값이 잘 저장되어 있는 모습을 볼 수 있다.

 

정리!

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

 

 

임베디드 타입과 연관관계

여기에서 특이한 점은 Value인 PhoneNumber가 Entity인 PhoneEntity를 가지고 있다는 점이다. 위에서 했던 예제에서 Address에서도 Member 객체를 불러올 수 있기 때문에 가능한 일이다.

 

@AttributeOverride : 속성 재정의

한 엔티티(클래스) 안에서 같은 값 타입을 사용할 수 있을까? 회원의 엔티티에 집 주소 외에 회사 주소를 저장해야한다고 가정해보자.

@Embedded
private Address homeAddress;

@Embedded
private Address workAddress;

이렇게 필드를 만들고 애플리케이션을 실행해보면 에러가 발생한다.

이런 경우 @AttributeOverrides와 @AttibuteOverride로 해결할 수 있다.

@Embedded
@AttributeOverrides({@AttributeOverride(name = "city",
                    column = @Column(name = "work_city")),
                    @AttributeOverride(name = "street",
                    column = @Column(name = "work_street")),
                    @AttributeOverride(name = "zipcode",
                    column = @Column(name = "work_zipcode"))})
private Address workAddress;

 

임베디드 타입과 null

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

만약 Member 클래스에서 Preriod의 값을 null로 주었다면, Preriod 클래스 안에 있는 모든 필드엔 null 값이 저장된다.

반응형