Spring/JPA

[JPA] @Embedded

SeongHun._. 2023. 1. 12. 15:15

@Embedded란?

새로운 값 타입을 직접 정의하여 사용할 수 있는데, JPA는 이것을 임베디드 타입(Embedded type)이라 한다.

  • @Embeddable: 값 타입을 정의하는 곳에 붙인다.
  • @Embedded: 값 타입을 사용하는 곳에 붙인다.
  • 임베디드 타입은 기본 생성자가 필수로 있어야 한다.

엔티티에 임베디드 타입을 사용하지 않았을 때 다음과 같을 것이다.

 

@Entity
public class Member {
  
  @Id @GeneratedValue
  private Long id;
  private String name;
  
  // 근무 기간
  LocalDateTime startDate;
  LocalDateTime endDate;
  
  // 주소
  private String city;
  private String street;
  private String zipcode;
  // ...
}

 

위와 같이 Member 객체는 근무 기간과 주소에 대한 각각의 데이터들이 모여 있으며, 하나의 객체가 모든 정보를 가지게 되는 문제가 있다.

Member 객체가 상세한 데이터를 모두 가지고 있는 것은 객체지향적이지 못하며, 응집력을 떨어뜨릴 수 있다.

 

임베디드 타입을 이를 객체지향에 맞는 설계를 도와준다.

 

 

@Embeddable
public class Peroid {
  
  private LocalDateTime startDate;
  private LocalDateTime endDate;
  ...
  
  public Peroid(){}
}

 

@Embeddable
public class Address {
  
  @Column(name="city") // 매핑할 컬럼 정의 가능
  private String city;
  private String street;
  private String zipcode;
  ...
  public Address(){}
}

 

위와 같이 각각의 근무 기간과 주소를 담은 객체를 엔티티 필드에 넣어주고 @Embedded 걸어주면 된다.

 

@Entity
public class Member {
  
  @Id @GeneratedVAlue
  private Long id;
  private String name;
  
  @Embedded
  private Period period;
  
  @Embedded
  private Address address;
}

 

 

임베디드 타입은 값 타입의 일부이기 때문에 실제 DB는 임베디드 타입을 활용하든 하지 않든 같은 테이블 구조를 가진다.

 

 

@AttributeOverride를 사용한 속성 재정의

예를들어 Member 객체에 집과 회사의 주소를 필요로 한다면 다음과 같이 @AttributeOverride를 사용하면 된다.

 

@Entity
public class Member{

    @Id @GeneratedValue
    private Long id;
    private String name;

    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name = "city", column = @Column(name = "HOME_CITY")),
            @AttributeOverride(name = "street", column = @Column(name = "HOME_STREET")),
            @AttributeOverride(name = "zipcode", column = @Column(name = "HOME_ZIP"))
    })
    private Address homeAddress;

    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name = "city", column = @Column(name = "COMPANY_CITY")),
            @AttributeOverride(name = "street", column = @Column(name = "COMPANY_STREET")),
            @AttributeOverride(name = "zipcode", column = @Column(name = "COMPANY_ZIP"))
    })
    private Address companyAddress;
}

 

 

임베디드 타입 공유 참조 주의

임베디드 타입은 여러 엔티티와 공유하는 경우 위험할 수 있다.

임베디드 타입 객체 공유하는 어떤 객체의 값을 수정하는 경우 다른 객체의 값까지 바뀔 수 있는 문제가 발생할 수 있다.

 

Member 객체에 임베디드 타입 Adress 객체를 각자 저장한다고 가정해보자.

이때 member1은 서울 → 대전으로 주소를 변경하려고 한다.

 

Address address = new Address("서울", "도로", "10-10");
Member member1 = new Member();
member1.setAddress(address);
em.persist(member1);


Member member2 = new Member();
member2.setAddress(address); //address 공유
em.persist(member2);

member1.getAddress().setCity("대전");

 

member1은 대전, member2는 서울이 주소에 들어가길 바랬으나, DB에서는 member1과 member2의 주소가 대전으로 변경되게 된다. 이는 같은 트랜잭션 범위 내에 공유되기 때문이다.

 

 

불변 객체로 만들기

객체를 불변하게 만들면 값을 수정할 수 없기 때문에 공유 참조로 발생하는 문제를 해결할 수 있다.

따라서 값 타입은 되도록 불변 객체 즉, 생성 시점 이후 값을 변경할 수 없는 객체로 설계해야 한다.

 

기존 객체를 수정하는 방식이 아닌 새로운 객체를 생성해서 값을 넣어주는 방식은 다음과 같다.

 

@Embeddable
public class Address {

    private String city;
    private String street;
    private String zipcode;
    
    protected Address(){}

    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }
    
    // 수정자는 만들지 않는다.
}

 

위와 같이 Address 객체는 생성 시점부터 변경할 수 없기 때문에 불변 객체이다.

 

 

참고

  • @Inheritance는 다형성을 위한 상속
  • @MappedSuperclass는 코드 재사용을 위한 상속
  • @Embedded, @Embeddable은 합성을 구현할 때 사용

 

reference

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

'Spring > JPA' 카테고리의 다른 글

[JPA] 즉시 로딩과 지연 로딩  (0) 2023.01.12
[JPA] 프록시(Proxy)  (0) 2023.01.12
[JPA] @MappedSuperclass  (0) 2023.01.12
[JPA] 상속관계 매핑  (0) 2023.01.11
[JPA] 연관관계 매핑 (다중성)  (0) 2023.01.11