[JPA] 임베디드 타입 (복합 값 타입)

3Beom's 개발 블로그·2023년 6월 15일
0

SpringJPA

목록 보기
12/21
post-custom-banner

출처

본 글은 인프런의 김영한님 강의 자바 ORM 표준 JPA 프로그래밍 - 기본편 을 수강하며 기록한 필기 내용을 정리한 글입니다.

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


임베디드 타입(복합 값 타입)

  • 새로운 값 타입을 직접 정의할 수 있는 기능을 말하며, JPA는 임베디드 타입(embedded type)이라 한다.
  • 주로 기본 값 타입을 모아서 새로운 값 타입을 만들기 때문에 복합 값 타입이라고도 한다.
  • int, String과 같은 “값 타입” 이다.
    • 값이 변경되면 추적 안된다.

<예시>

  • 회원 엔티티는 다음과 같이 “이름, 근무 시작일, 근무 종료일, 주소 도시, 주소 번지, 주소 우편번호”를 갖는다고 설정한다.

  • 여기서 하나로 묶을 수 있는 타입들을 확인할 수 있다.
    • 근무 시작일, 근무 종료일 → 근무 기간
    • 주소 도시, 주소 번지, 주소 우편번호 → 집 주소
  • 이처럼 다음과 같이 여러 값들을 하나의 값으로 묶어서 활용할 수 있는 것이 “임베디드 값 타입” 이다.

  • Period 라는 타입과 Address 라는 타입을 만들어 활용하는 것을 확인할 수 있다.

  • 따라서 위 사진과 같이 Member 엔티티는 “id, name, workPeriod, homeAddress” 값을 갖고 있으며, workPeriod는 Period 값 타입, homeAddress는 Address 값 타입인 것이다.
  • 이는 사실 자바 내에서는 Period class, Address class 를 직접 정의해서 쓰면 되지만, JPA 에서는 이를 어떻게 활용할 수 있는지에 대해 알아야 한다.

임베디드 타입의 장점

  • 재사용이 가능하다.
    • 프로젝트 내에서 반복되는 타입들을 하나로 묶어 만들어 두면 여러 곳에서 재사용할 수 있다.
  • 높은 응집도
  • Period.isWork() 처럼 해당 값 타입만 사용하는 의미 있는 메소드를 만들 수 있다.
  • 임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티티에 생명주기를 의존한다.
    • 임베디드 타입도 “값 타입” 이다.
    • 똑같이 엔티티 생성될 때 생성되고, 엔티티 삭제되면 같이 삭제된다.

임베디드 타입 사용법

  • @Embeddable : 값 타입을 정의하는 곳에 적용
  • @Embedded : 값 타입을 사용하는 곳에 적용
  • 기본 생성자 필수

  • 위 사진과 같이 JPA 내에서는 임베디드 값 타입으로 Period와 Address를 만들어서 활용하게 되지만, 실제로 DB에 저장되는 필드들은 임베디드 값 타입 이전과 동일한 것을 확인할 수 있다.

  • 임베디드 값 타입 매핑만 잘 해두면 DB 구조를 유지하면서 JPA 내에서만 임베디드 값 타입을 만들어 유용하게 활용할 수 있는 것!

    • DB 입장에서는 데이터를 있는 그대로 저장하는게 맞음.
    • 하지만 JPA 입장에서는 보다 효율적으로 관리하고 추가 기능도 넣는 등의 이점을 많이 가질 수 있다.
  • 묶기 전 Member 엔티티

@Entity
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name="USERNAME")
    private String username;

    private LocalDateTime startDate;
    private LocalDateTime endDate;

    private String city;
    private String street;
    private String zipcode;

...
  • Period, Address class 생성해서 필드들 묶어주고 Member class에 반영
public class Period{
	private LocalDateTime startDate;
	private LocalDateTime endDate;
}
public class Address{
	private String city;
	private String street;
	private String zipcode;
}
public class Member{
		@Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name="USERNAME")
    private String username;

		private Period workPeriod;
		
		private Address homeAddress;
...
  • 이렇게만 해두면 에러난다. JPA에게 임베디드 값 타입이라는 것을 알려주어야 함.

  • 임베디드 값 타입을 적용하기 위해 Period와 Address 처럼 임베디드 값 타입을 만드는 곳은 “@Embeddable”,

    Member의 workPeriod와 homeAddress 처럼 임베디드 값 타입을 쓰는 곳은 “@Embedded”

    어노테이션을 활용한다.

@Embeddable
public class Period{
	@Column(name = "START_DATE_TIME")
	private LocalDateTime startDate;
	@Column(name = "END_DATE_TIME")
	private LocalDateTime endDate;
}

→또, 위와 같이 각 필드에 @Column 어노테이션을 넣을 수도 있다.

@Embeddable
public class Address{
...
...
@Embedded
private Period workPeriod;

@Embedded
private Address homeAddress;
...
  • 원래는 @Embeddable, @Embedded 둘 중 하나만 써도 되지만, 웬만하면 두 곳 모두 적어주는게 좋다.

  • 다음과 같이 Period class 내에 “isWork()” 와 같이 전용 메소드를 추가해 줄 수도 있다.

@Embeddable
public class Period{
	...
	public boolean isWork(){

}
	...
  • 임베디드 값 타입 만들 때, 기본 생성자는 무조건 만들어야 하고, 추가로 생성자를 만들어 두는게 편리하다.
  • 기본 생성자
public Address(){
}
  • 생성자
public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }
  • 활용
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("city", "street", "zipcode");
member.setWorkPeriod(new Period(~~));
...

임베디드 타입과 테이블 매핑

  • 임베디드 타입은 엔티티의 값 중에 하나일 뿐이다.
    • 그냥 여러개의 필드를 하나의 클래스로 묶어서 재사용성, 응집도를 높인 것일 뿐이다.
    • 이를 구현하기 위해 @Embeddable, @Embedded 어노테이션이 활용된 것이다.
  • 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 그대로 같다.
    • 그냥 묶어주기만 한 것이기 때문에, 임베디드 타입 사용 여부와 관계없이 테이블 구조는 동일해야 한다.
  • 객체와 테이블을 아주 세밀하게(find-grained) 매핑하는 것이 가능하다.
    • 논리적인 설계 과정에서도 ‘근무 시작일, 근무 종료일, 도시주소, 도로명주소, 우편번호’ 이렇게 줄줄이 구성하는 것보다 간단히 “근무기간, 집주소”로 간단명료하게 하는게 더 직관적이다.
  • 잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많다.

임베디드 타입과 연관관계

  • Member는 임베디드 값 타입으로 “Address”와 “PhoneNumber” 를 갖고 있다.
  • “Address”는 내부에 또다른 임베디드 값 타입으로 “Zipcode”를 갖고 있다.
  • “PhoneNumber”는 내부에 “PhoneEntity” 엔티티를 갖고 있다.
  • 즉, 임베디드 값 타입 안에 또다른 임베디드 값 타입을 가질 수 있고, 또는 엔티티를 가질 수도 있다.

@AttributeOverride : 속성 재정의

  • 그렇다면, 만약 한 엔티티에서 같은 값 타입을 사용한다면?
    • 위 예시에서는 Member 내에 집주소(homeAddress)만 있었지만, 만약 직장주소(workAddress)도 필요하다면?

      @Embedded
      private Address homeAddress;
      
      @Embedded
      private Address 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;

  • 여러개면 @AttributeOverrides, 하나면 @AttributeOverride 를 활용하면 된다.

임베디드 타입과 null

  • 임베디드 타입의 값이 null이면 매핑한 컬럼 값은 모두 null이다.
    • homeAddress = null 이면, 그 안에 있는 city, street, zipcode 모두 null 값을 갖는다.
profile
경험과 기록으로 성장하기
post-custom-banner

0개의 댓글