JPA

[JPA] 임베디드 타입으로 엔티티 개선하기

Woong이 2024. 7. 19. 13:45
반응형

이 글에서는 JPA의 데이터 타입 중 하나인 값 타입, 그 중에서도 임베디드 타입(embedded type)에 대해서 알아볼 것이다.

 

 

JPA의 데이터 타입


임베디드 타입을 알아보기에 앞서, JPA의 데이터 타입에 대해 알아보자.

 

JPA의 데이터 타입은 크게 두 종류로 분류할 수 있다.

  • 엔티티 타입
  • 값 타입

 

엔티티 타입

엔티티 타입의 데이터는 기본적으로 @Entity 어노테이션으로 정의한 엔티티 객체를 말하며, 데이터의 변화가 생겨도 식별자를 통해 지속적으로 변경에 대한 추적이 가능하다.

 

값 타입

값 타입은 int, String처럼 단순하게 값을 나타내는 Java의 기본 타입이나 객체를 의미한다. 식별자를 가질 수 없으며, 엔티티 타입의 데이터와 함께 존재하기 때문에 생명 주기도 엔티티 타입의 객체에 의존하게 된다. 따라서 엔티티 타입 객체의 인스턴스를 제거하면 이에 속한 값 타입 데이터들도 함께 제거된다.

 

또한, 값 타입에는 여러 종류가 존재한다.

  •  기본 값 타입
    • int, double 같은 기본 타입
    • Integer, Long, Double 같은 Wrapper 타입
    • String
  • 임베디드 타입
  • 값 타입 컬렉션

 

 

임베디드 타입이란?


임베디드 타입은 여러 기본 값 타입이 하나로 모인 복합 값 타입을 의미한다. 예제 코드를 통해 더 자세히 알아보자.

@Entity
public class Member {

    @Id 
    @GeneratedValue
    private Long id;

    private String name;

    private String email;

    private String addressCity;

    private String addressStreet;

    private String zipcode;
}

위의 코드는 여러 기본 값 타입을 포함하고 있는 Member 엔티티이다. Member 엔티티의 필드 중에서 addressCity, addressStreet, zipcode는 회원의 주소를 나타내는 데이터라는 것을 짐작할 수 있을 것이다. 그렇기 때문에 위와 같이 필드를 따로 두는 방식보다는 아래와 Address라는 클래스를 만들어 하나의 데이터로 나타내는 것이 더 좋을 것이다.

 

public class Address {
	
    private String city;
    
    private String street;
    
    private String zipcode;
}

 

이런 상황에서 임베디드 타입으로 값 타입을 정의하면 Address 객체를 Member 엔티티의 값 타입으로 만들 수 있다.

 

 

임베디드 타입 정의


임베디드 타입을 사용하기 위해서 필요한 어노테이션이 두 가지가 있다.

  • @Embeddable: 값 타입을 정의하는 곳에 표시
  • @Embedded: 값 타입을 사용하는 곳에 표시

이제 이 두 어노테이션을 사용하여 위에서 나온 Member 엔티티를 개선해보자. 먼저 Address 클래스를 임베디드 타입 객체로 사용하기 위해 @Embeddable를 붙여야 한다.

@Embeddable
public class Address {
	
    private String city;

    private String street;

    private String zipcode;
}

 

그리고 Address를 값 타입으로 사용하기 위해서 다음과 같이 Member 엔티티에 있는 Address 필드에 @Embedded 어노테이션을 추가해야 한다.

@Entity
public class Member {

    @Id 
    @GeneratedValue
    private Long id;

    private String name;

    private String email;
	  
    @Embedded
    private Address address;
}

 

 

임베디드 타입의 특징


임베디드 타입을 사용하기 전 알면 좋을 여러가지 특징들이 있다.

  • 임베디드 타입은 엔티티의 값중 일부일 뿐이다.
    • 그러므로, 임베디드 타입을 사용하기 전과 후에 매핑되는 테이블에는 변화가 없다.
  • 임베디드 타입을 사용하면 객체와 테이블을 더 세밀하게 매핑하는 것이 가능하다.
  • 임베디드 타입 안에 엔티티 타입의 객체를 넣어서 사용할 수 있다.

 

임베디드 타입 속성 재정의

만약 하나의 엔티티 안에 같은 임베디드 타입의 데이터를 두 개 이상 사용하고 싶다면, @AttributeOverride 어노테이션을 통해 임베디드 타입의 속성을 재정의하면 된다.

@Entity
public class Member {

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

    @Embedded 
    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_ZIPCODE"))
    })
    Address companyAddress;
}

 

임베디드 타입이 null일 경우

임베디트 타입의 데이터가 null일 경우, 임베디드 타입에 속한 모든 필드의 값이 null로 매핑이 된다.

member.setAddress(null);
em.persist(member);

 

임베디드 타입 사용 시 주의사항

임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험한 상황이 생길 수 있다.

member1.setHomeAddress(new Address("OldCity"));
Address address = member1.getHomeAddress();

address.setCity("NewCity");
member2.setHomeAddress(address);

 

위의 코드를 보면 member2의 주소만 "NewCity"로 변경되길 기대했지만, member1의 주소도 "NewCity"로 변경되어 버린다.

  • 이런 부작용을 막으려면 임베디드 타입을 생성자로만 값을 설정하고 수정자를 만들지 않도록 하여 불변 객체로 만들어야 한다.
반응형