@Embeddable 애노테이션을 지정한 클래스를 밸류 클래스라고 합니다. 밸류 클래스란 int, double 처럼 하나의 값을 나타내는 클래스를 말합니다. 보통 주소(address)라는 값을 저장하기 위해서는 아래와 같이 String 변수에 저장하여 관리할 것입니다. 

1
String address = "서울시 강동구 천호동 113-12번지 개발아파트 110동 1101호";
cs


하지만 주소 역시 값을 분리하여 저장하는 경우도 많습니다. address1과 address2,, zipCode가 합쳐져야 하나의 정확한 의미를 나타내는데 변수를 분리하여 관리하기 때문에 불편한 점도 있을 것입니다. 이러한 경우에 "밸류 클래스"를 사용하여 값이 가지는 의미를 강화시킬 수 있습니다.


1
2
3
String address1 = "서울시 강동구 천호동";
String address2 = "개발아파트 110동 1101";
String zipCode = "113-12";
cs


밸류 클래스

밸류 클래스는 여러개의 값(address1, address2, zipCode)을 가지지만 개념적으로 하나의 값(주소)을 표현합니다. 또한 다른 밸류 객체와 식별하기 위한 식별자를 가지지 않는 것이 특징입니다. 아래는 주소라는 값을 나타내는 Address 클래스입니다. 변수를 address1, address2, zipCode로 각각 관리했을 때보다 좀 더 의미가 뚜렷한 것 같지않으신가요? 

1
2
3
4
5
class Address {
    String address1;
    String address2;
    String zipCode;
}
cs


하지만 밸류 클래스는 보통 값으로 사용되기 때문에 equals 메소드와 hashCode 메소드를 오버라이딩하여 사용해야 합니다. 


@Embeddable 애노테이션과 밸류 매핑

먼저 @Embeddable 애노테이션은 설정한 클래스가 다른 엔티티의 일부로 저장될 수 있음을 설정하는 애노테이션입니다. 즉 @Embeddable 애노테이션을 설정한 밸류 클래스는 다른 엔티티에 포함될 수 있다는 것이죠. 밸류를 포함하는 엔티티에서는 @Embedded로 밸류 클래스임을 설정합니다. 호텔(엔티티)과 주소(밸류)의 설정을 살펴보시면 이해가 될 것 입니다.

1
2
3
4
5
6
7
8
create table jpastart.hotel (
    id varchar(100not null primary key,
    name varchar(50),
    grade varchar(255),
    zipcode varchar(5),
    address1 varchar(255),
    address2 varchar(255)
engine innodb character set utf8;
cs


호텔 엔티티
1
2
3
4
5
6
7
8
9
10
11
@Entity
public class Hotel {
    @Id
    private String id;
    private String name;
    @Enumberated(EnumType.STRING)
    private Grade grade;
 
    @Embedded
    private Address address;
}
cs

주소 밸류클래스

1
2
3
4
5
6
7
8
@Embeddable
public class Address{
    private String zipCode;
    private String address1;
    private String address2;
 
    //...
}
cs



그럼 엔티티매니저를 사용하여 Hotel을 검색하면 어떻게 될까요? address1, address2, zipCode는 자동으로 Hotel 엔티티안의 Address 밸류클래스로 매핑되기 때문에 호텔의 get메소드를 호출하여 사용할 수 있습니다. 


1
2
3
4
5
Hotel hotel = em.find(Hotel.class"H100-01");
Address address = hotel.getAddress();
// address.getAddress1(); 서울시 강동구 천호동
// address.getAddress2(); 개발호텔
// address.getZipCode();  113-12
cs



@Embeddable 접근 타입

@Embeddable 매핑한 대상은 기본적으로 엔티티의 접근 방법( 필드 접근타입 또는 프로퍼티 접근 타입 )을 따릅니다. 엔티티에서 프로퍼티 접근 타입(getter / setter)를 사용한다면 @Embeddable 매핑한 밸류 클래스 역시 프로퍼티 접근 타입을 사용하기 때문에 getter, setter 메소드를 정의해야 합니다.



@AttributeOverrides

관광지라는 엔티티가 있고 이 엔티티는 한글로 된 주소, 영어로 된 주소 2개를 가진다고 해보자. 

1
2
3
4
5
6
7
8
9
10
create table jpastart.sight (
    id varchar(100not null primary key,
    name varchar(50),
    kor_zipcode varchar(5),
    kor_address1 varchar(255),
    kor_address2 varchar(255)
    eng_zipcode varchar(5),
    eng_address1 varchar(255),
    eng_address2 varchar(255)
engine innodb character set utf8;
cs


위의 sight 테이블을 엔티티로 매핑하면 아래와 같은데 Address 밸류 클래스는 address1, address2, zipcode를 필드로 설정하고 있다. Sight 엔티티에서 Address 밸류클래스를 중복 선언하였기 떄문에  필드가 중복됐다는 JPA의 에러 메세지를 볼 수 있을것이다. 

1
2
3
4
5
6
7
8
9
@Entity
class Sight {
    private String id;
    private String name;
    @Embedded
    private Address korAddress;
    @Embedded
    private Address engAddress;
}
cs



@AttributeOverrides 또는 @AttributeOverride 애노테이션을 사용하면 위와 같은 문제를 해결할 수 있다. 이 두 애노테이션은 @Embedded로 매핑한 값 타입의 매핑 설정을 재정의할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Entity
class Sight {
    private String id;
    private String name;
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name="zipcode", column=@Column(name="kor_zipcode"),
        @AttributeOverride(name="address1", column=@Column(name="kor_address1"),
        @AttributeOverride(name="address2", column=@Column(name="kor_address2")
    })
    private Address korAddress;
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name="zipcode", column=@Column(name="eng_zipcode"),
        @AttributeOverride(name="address1", column=@Column(name="eng_address1"),
        @AttributeOverride(name="address2", column=@Column(name="eng_address2")
    })
    private Address engAddress;
}
cs


위와 같이 설정하면 Address 밸류 클래스 필드값의 컬럼 설정을 재정의할 수 있다. 기존에는 address1, address2, zipcode가 중복되어서 오류가 발생하였지만 @AttributeOverrides, @AttributeOverride 애노테이션을 이용하여 컬럼이 중복되지 않도록 재정의하였다. 


데이터를 객체를 모델링하다보면 밸류 클래스의 중첩이 일어날 수 있는데 JPA에서는 중첩 @Embeddable도 지원된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Entity
public class City {
    // ...
    
    // 도시 연락처정보
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name="address.zipcode", column=@Column(name="city_zip")),
        @AttributeOverride(name="address.address1", column=@Column(name="city_addr1")),
        @AttributeOverride(name="address.address2", column=@Column(name="city_addr2"))
    })
    private ContractInfo contractInfo;
}
 
@Embeddable
public class ContractInfo {
    @Column(name="ct_phone")
    private String phone;
 
    @Column(name="ct_email");
    private String email;
 
    @Embedded
    // City 클래스에서 재정의 안했을 경우 ContractInfo 클래스에서도 
    //@AttributeOverrides({
    //    @AttributeOverride(name="zipcode", column=@Column(name="ct_zip")),
    //    @AttributeOverride(name="address1", column=@Column(name="city_addr1")),
    //    @AttributeOverride(name="address2", column=@Column(name="city_addr2"))
    //})
    private Address address;
}
 
 
cs



밸류를 다른 테이블에 저장하기

sight 테이블을 Sight 엔티티와 Address 밸류로 매핑한 것처럼 하나의 테이블을 엔티티와 밸류로 저장할 수도 있지만, 
엔티티와 밸류를 각각의 테이블로도 저장할 수 있다. UML로 표현하면 아래와 같은 구조를 가진다



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Embeddable
public class SightDetail {
    @Column(name="hours_op")
    private String hourcOfOperation;
    private String holidays;
    private String facilites;
}
 
@Entity
@SecondaryTable(
    name ="sight_detail",
    pkJoinColumns= @PrimaryKeyJoinColumn(     //sight_detail의 sight_id(기본키=외래키)가 sight테이블의 id(식별자)를 참조함
        name = "sight_id", referencedColumnName = "id")
public class Sight {
    // 생략..
 
    // table = sight_detail은 데이터를 해당 테이블에서 가져오겠다는 의미
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(
            name="hoursOfOperation",
            column=@Column(name="hours_op", table="sight_detail")),
        @AttributeOveride( 
            name="holidays",
            column=@Column(table="sight_detail")),
        @AttributeOverride(
            name="facilities",
            column=@Column(table="sight_detail"))
    })
    private SightDetail detail;
}
cs




'웹 개발 > JPA 프로그래밍' 카테고리의 다른 글

EntityManager 와 영속 컨텍스트  (0) 2019.03.03
JPA 식별자 생성 방식  (0) 2019.01.29
엔티티(Entity)와 엔티티매니저(EntityManager)  (0) 2019.01.01
JPA란  (0) 2018.12.29

+ Recent posts