Skip to content

Instantly share code, notes, and snippets.

@taekwon-dev
Created August 23, 2022 11:41
Show Gist options
  • Save taekwon-dev/707fc153a45c2281b1609c03a0b1c934 to your computer and use it in GitHub Desktop.
Save taekwon-dev/707fc153a45c2281b1609c03a0b1c934 to your computer and use it in GitHub Desktop.

[JPA] Embedded Type

❗️책 자바 ORM 표준 JPA 프로그래밍 공부한 내용을 정리한 글 입니다.

임베디드 타입

image [ 그림 1 ]

JPA 데이터 타입에는 [ 그림 1 ] 과 같이 크게 엔티티 타입과 값 타입으로 구분할 수 있고, 값 타입에는 기본 값 타입, 컬렉션 값 타입 그리고 이번 글의 주제인 임베디드 타입이 있다.

@Entity
public class Member {

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

	private String city;
	private String street;
	private String zipCode;
	...
}

위 코드에서 Member 는 엔티티 타입으로 @Id 어노테이션을 통해서도 알 수 있듯이 식별자가 있다는 것이 가장 큰 특징이다. 엔티티 타입은 영속성 컨텍스트에 식별자 기준으로 각 엔티티가 저장되고 컨텍스트 내 생명주기에 영향을 받는다. 엔티티 타입 내부를 보면 여러 필드가 있는데, String name , int age 등이 기본 값 타입에 해당한다. 구조적으로 엔티티 클래스 내부에 정의된 기본 값 타입은 엔티티 생명주기에 의존한다는 특징이 있다.

임베디드 타입은 복합 값 타입으로도 불리는데, JPA에서 사용자가 값 타입을 직접 정의한다. Member 엔티티 클래스에서 임베디드 타입을 다음과 같이 활용할 수 있다.

image [ 그림 2 ]

Member 엔티티 클래스 필드 중 [ 그림 2 ] 에 해당하는 필드는 회원의 집 주소 개념으로 묶을 수 있다. 이러한 묶음을 통해 집 주소에 해당하는 필드 간 응집도를 높일 수 있다. 이러한 묶음 개념을 표현할 때 JPA 에서 제공하는 임베디드 타입을 사용할 수 있다.

@Embeddable
public class Address {
	private String city;
	private String street;
	private String zipCode;
}

@Entity 
public class Member {
  @Id
	@GeneratedValue
	private Long id;
	private String name;
	private int age;

	@Embedded
	private Address address;
}

위 예시 코드와 같이 @Embeddable@Embedded 어노테이션을 각각 임베디드 타입으로 생성한 클래스 레벨(= 값 타입을 정의하는 곳) 그리고 해당 임베디드 타입을 포함하는 엔티티 클래스 필드 레벨(= 값 타입을 사용하는 곳)에 선언함으로써 임베디드 타입을 정의할 수 있다. 또한 위 코드에서 Member 엔티티 클래스를 보면 임베디드 타입으로 정의한 인스터스 필드를 갖고 있는 것을 볼 수 있는데, 이를 통해 엔티티와 임베디드 타입의 관계를 컴포지션 관계가 된다는 것을 알 수 있다.

위와 같이 임베디드 타입을 직접 정의해서 사용하면 어떤 장점이 있을까? 먼저 컴포지션 관계인 점에서 예상할 수 있듯이 임베디드 타입 코드를 재사용 가능하다는 점이 있다. 또한 여러 필드를 하나의 개념으로 묶어서 새로운 값 타입을 정의한 점에서 응집도를 높게 관리할 수 있다는 점이 있다.

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

	public String getAddress() {
		return city + " " + street + " " + zipCode;
  }
}

응집도를 높게 관리할 수 있다는 것은 해당 엔티티 클래스가 갖고 있는 책임을 보다 명확하게 구분하고 나눌 수 있다고도 볼 수 있다. Address 타입을 새로 정의 했기 때문에 더 이상 Member 엔티티 클래스에서 집 주소와 관련된 상세 내용을 알 필요가 없다. 컴포지션 관계인 인스턴스를 통해서 getAddress() 와 같은 요청을 보낼 수 있기 때문이다. 만약 비즈니스 요구사항이 변경 되면서 집 주소 필드를 추가해야 하는 경우에도 위와 같이 Address 타입을 정의해서 사용하면 수정 범위는 Address 로 제한할 수 있다는 장점도 있다.

잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많다.

만약 임베디드 타입을 사용하지 않았다면 Member 엔티티 & MEMBER 테이블은 일대일 대응이 되지만 Address 타입을 새로 정의하면서 클래스 수가 늘어나기 때문에 더 이상 일대일 대응이 되지 않고 MEMBER 테이블과 관련한 테이블 수가 더 많아진다. 이는 임베디드 타입을 사용하면 세밀하게 테이블과 객체를 매핑할 수 있다는 것을 의미한다.


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

image [ 그림 3 ]

임베디드 타입은 자바의 데이터 타입 중 객체 타입이다. 따라서 [ 그림 3 ] 과 같이 객체의 주소를 여러 엔티티에 공유하게 되면 의도치 않은 문제가 발생할 수 있다. 같은 주소를 참조하고 있기 때문에 이를 사용하는 곳에서 값을 변경하게 되면 의도치 않은 값 변경으로 인해 혼란을 줄 수 있다.

image [ 그림 4 ]

따라서 [ 그림 3 ] 과 같이 임베디드 타입을 공유하는 것은 지양하고, 사용할 때는 공유가 아닌 복사를 통해 [ 그림 4 ] 와 같은 구조를 만들어야 의도치 않은 문제를 미리 방지할 수 있다. 하지만 이러한 문제를 근본적으로 해결하기 위해서는 애초에 이러한 문제를 야기할 수 있는 상황을 제공하지 않는 것이 더 효율적일 수 있다. 예를 들어, 해당 객체의 수정자 메소드를 제공하지 않음으로써 최초 생성 시 주입 받은 값을 생성 이후에 수정될 수 없도록 하는 불변 객체로 만드는 방법이 있다.


임베디드 타입과 연관관계

임베디드 타입은 엔티티 타입을 참조할 수 있고, 해당 임베디드 타입을 사용하는 엔티티와 임베디드 타입이 참조한 엔티티 간의 연관관계를 정의할 수 있다.

image [ 그림 5 ]

일반적으로 두 엔티티 간 연관관계는 [ 그림 5 ] 와 같이 엔티티 간 직접적으로 참조 관계를 정의하는 것과 달리 임베디드 타입을 통해 연관관계를 맺는 경우 아래 [ 그림 6 ] 과 같은 구조가 된다.

image [ 그림 6 ]

포스트 & 포스트 좋아요 기능을 구현해야 하는 상황에서 포스트 엔티티와 좋아요 엔티티 간 [ 그림 5 ] 와 같이 연관관계를 설정할 수 있다. 포스트에 대해 서로 다른 사용자가 좋아요를 누를 수 있기 때문에 Post : Like = 1 : N 로 설정한 상황이다. [ 그림 5 ] 와 같이 두 엔티티 간 연관관계를 설정하면 되는데 [ 그림 6 ] 과 같이 임베디드 타입을 통해 연관관계를 설정하는 이유는 뭐가 있을까?

image [ 그림 7 ]

[ 그림 7 ] 은 필자가 직접 참여한 커뮤니티 서비스 MVP 버전의 포스트 상세 화면이다.

image [ 그림 8 ]

[ 그림 7 ] 을 각 영역 별로 분류 하면 [ 그림 8 ] 처럼 나눌 수 있다. 포스트 상세 조회 시 포스트 엔티티는 포스트 좋아요 수 / 싫어요 여부 뿐만 아니라 포스트 작성자 정보와 포스트 내용 그리고 포스트에 달리 댓글 정보도 필요하다.

여기서 강조하고 싶은 부분은 각 정보를 가져오기 위해 포스트 엔티티가 갖고 있는 책임이 많고 다양하다는 점이다. 앞서 임베디드 타입을 사용하면 응집도를 높일 수 있다고 했는데, 이를 활용하여 각 기능 단위로 묶어서 관리하면 다양한 기능을 보다 효율적으로 관리할 수 있다. 뿐만 아니라 임베디드 타입 내 엔티티 참조 및 연관관계 설정이 가능하므로 이를 활용하면 포스트 엔티티가 갖는 여러 연관관계 포스트 엔티티가 아닌 임베디드 타입에 정의할 수 있기 때문에 포스트 엔티티를 복잡하지 않게 관리할 수 있다는 장점이 있다.


| Reference

자바 ORM 표준 JPA 프로그래밍 | 9장 값 타입

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment