본문 바로가기
데이터베이스/JPA

JPA 임베디드 타입( Embedded Type, 복합 값 타입) : @Embedded, @Embeddable, @AttributeOverride, @AttributeOverrides

by jeonghaemin 2021. 2. 20.
728x90

여러 개의 값을 묶어서 하나의 값 타입을 만들수 있는데 JPA에서는 이것을 임베디드 타입이라고한다.

JPA 임베디드 타입의 기본적인 사용법에 대해 알아보자.

사용 방법

"Person 엔티티는 이름과 주소를 가지고있습니다."

이것을 임베디드 타입을 사용하지 않고 엔티티 코드를 작성하면 다음과 같이 작성할 수 있다.

@Entity
public class Person {

    @Id @GeneratedValue
    private Long id;

    private String name;

    private String city;

    private String street;

    private String zipcode;

}

코드를 보면 city, street, zipcode는 주소와 관련된 값들 임을 알 수 있다. 이런 경우 임베디드 타입을 사용하여 연관된 값을 하나로 묶어 줄 수 있다.

임베디드 타입을 사용해서 city, street, zipcode를 하나로 묶어 보겠다.

Address

@Embeddable
public class Address {

    private String city;

    private String street;

    private String zipcode;

    public Address() {}

    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }
}

Person

@Entity
public class Person {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @Embedded
    private Address address;

    protected Person() {}

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }
}

임베디드 타입을 사용하려면 해당 타입에서 @Embeddable 애노테이션을 붙히거나, 타입을 사용하는 곳에서 @Embedded 애노테이션을 붙혀줘야 한다. 또는 위 코드처럼 두 곳 모두 붙혀줘도 된다.

임베디드 타입은 엔티티와 비슷하다?

임베디드 타입은 엔티티와 비슷하지 않다. 임베디드 타입은 정말 말 그대로 그냥 단순히 '값' 들을 하나로 묶어 놓은 것이다.

그렇기 때문에 엔티티와 달리 식별자가 없고, 생명주기를 자신을 소유하는 엔티티에 의존한다.

실제 임베디드 타입을 사용했을때 JPA가 생성해준 create문을 보면 다음과 같다.

create table Person (
       id bigint not null,
        city varchar(255),
        street varchar(255),
        zipcode varchar(255),
        name varchar(255),
        primary key (id)
)

하나의 엔티티에서 같은 임베디드 타입을 여러개 사용해야한다면?

다음과 같이 하나의 값 타입을 여러개 사용해야 되는 상황이 있을 수 있다. 단순히 변수를 하나 더 추가한다면?

@Embedded
private Address homeAddress;

@Embedded
private Address workAddress;

반복되는 컬럼이 있다는 메시지와 함께 예외가 발생한다.

Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: hello.Person column: city (should be mapped with insert="false" update="false")

그 이유는 homeAddress, workAddress의 컬럼명이 서로 중복되기 때문이다.

@AttributeOverrides와 @AttributeOverride를 사용해서 컬럼을 재정의해주면 된다.

@Embedded
    private Address homeAddress;

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

생성되는 테이블의 구조는 다음과 같다.

create table Person (
       id bigint not null,
        city varchar(255),
        street varchar(255),
        zipcode varchar(255),
        name varchar(255),
        work_city varchar(255),
        work_street varchar(255),
        work_zipcode varchar(255),
        primary key (id)
)

임베디드 타입 타입의 특징

  • 식별자가 없다.
  • 생명주기를 엔티티에 의존한다.
  • 객체이기 때문에 여러 엔티티에서 공유해서 사용하면 사이드 이펙트가 생길 수 있다. -> 항상 값을 복사해서 사용하자.
  • 자바의 String 처럼 값 타입은 불변 객체로 만들어야한다. -> 생성자로만 값을 설정하고 Setter를 만들지 말자.
  • 인스턴스가 달라도 값이 같으면 같은 것으로 봐야함. -> 값 타입의 비교는 equals() 메서드를 통해서 하자.(equals(), hashCode() 메서드를 적절히 오버라이드하여 사용)

참고

댓글