3. Embeddable

Aggregate Mapping

OOP에선 의미가 드러나지 않는 Primitive Type 대신 Value Object를 활용하는걸 권하는데 JPA에서는 Aggregate Mapping을 통해 이를 지원할 수 있다.

예를 들어 Person객체가 있고 필드로 name, age, gender를 갖는다고 해보자.

각 필드의 타입을 String으로 가져간다면 필드의 특성을 반영하기 힘들다. Gender타입을 선언해 gender를 사용해보자.

기존의 객체 생성이 다음과 같았다면

Person person = new Person("Mr.Big", 35, "남");
@Embeddable
public class Gender {
    @Column(name = "gender")
    private String value;

    private Gender() {
    }

    private Gender(String value) {
        this.value = value;
    }

    public static Gender male() {
        return new Gender("남");
    }

    public static Gender female() {
        return new Gender("여");
    }

    @Override
    public String toString() {
        return value;
    }
    ... equals(), hashCode()
}

Embeddable 어노테이션을 정적 팩토리 메서드를 사용하면 다음과 같이 명시적으로 선언이 가능하다.

Person person = new Person("Mr.Big", 35, Gender.male());

이런 식으로 하나의 Value Object를 만들어서 Gender에 대한 모든 처리를 위임했을 때 Person 객체가 SRP를 따르고 단순해질 거란 것 알 수 있다.

Value Object

값 타입

JPA의 데이터 타입은 엔티티 타입과 값 타입으로 나눌 수 있는데 엔티티 타입은 @Entity로 정의하는 객체이고 값 타입은 int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체를 말한다. 엔티티 타입은 식별자를 통해 추적 가능하지만 값 타입은 식별자가 없고 숫자나 문가탕느 속성만 있어 추적할 수 없다.

값 타입은 3가지로 나눌 수 있다.

  • 기본값 타입

    • 자바 기본 타입(int, double)

    • 래퍼 클래스(Integer)

    • String

    @Entity
    public class Member {
      @Id @GeneratedValue
      private Long id;
    
      private String name;
    
      private int age;
    }
  • 임베디드 타입(복합 값 타입) 새로운 값 타입을 직접 정의해서 사용할 수 있는데 이것을 임베디드 타입이라 한다.

    @Entity
    public class Member {
      @Id @GeneratedValue
      private Long id;
      private String name;
    
      @Embedded Period workPeriod;
    
      @Embedded Address homeAddress;
    }
    
    @Embeddable
    public class Period {
    
      @Temporal(TemporalType.DATE) java.util.Date startDate;
      @Temporal(TemporalType.DATE) java.util.Date endDate;
    }
    
    @Embeddable
    public class Address {
    
      @Column(name="city")
      private String city;
      private String street;
      private String zipcode;
    }
    • 임베디드 타입은 기본 생성자가 필수다

    • 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다

  • 컬렉션 값 타입

@Entity

JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 어노테이션을 필수로 붙여야 한다.

속성

주의 사항은

  • 기본 생성자는 필수로 만들어줘야 한다.(파라미터가 없는 public 또는 protected 생성자)

  • final클래스, enum, interface, inner클래스에는 사용할 수 없다.

  • 저장할 필드에 final을 사용하면 안된다.

@Table

엔티티와 매핑할 테이블을 지정한다. 생략하면 매핑한 엔티티 이름을 테이블 이름으로 사용한다.

DDL-auto

Data Definition Language에는 여러 설정이 있는데 주로 초기 개발단계에서 사용한다.

@Id

기본 키를 직접 할당하려면 @Id로 매피하면 된다.

@Id 적용 가능 자바 타입은 다음과 같다.

  • 자바 기본형

  • 자바 래퍼형

  • String

  • java.util.Date

  • java.sql.Date

  • java.math.BigDecimal

  • java.math.BigInteger

@GenerationType

데이터베이스의 종류도 많고 기본 키를 만드는 방법도 다양한다. GenerationType.AUTO를 선택한 데이터베이스는 방언에 따라 IDENTITY, SEQUENCE, TABEl 전략 중 하나를 자동으로 선택하는데 ORACLE은 SEQUNCE를 MySQL은 IDENTITY를 사용한다.

대리키 전략

Id 어노테이션을 단독으로 사용하면 기본 키를 애플리케이션에서 직접 할당해줘야 한다. 대신 데이터베이스가 생성해주는 값을 사용하려면

IDENTITY

기본 키 생성을 데이터베이스에 위임하는 전략으로 MySQL, PostgreSQL, SQL Server, DB2에서 사용한다

@Entity
public class Board {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
}
  • Identity 전략은 데이터베이스에 INSERT를 날린 후에 기본 키 값을 조회할 수 있는데 Statement.getGeneratedKey()를 사용하면 저장과 동시에 한번의 통신으로 키 값도 가져올 수 있다.

  • 엔티티가 영속 상태가 되려면 식별자가 필요하기 때문에 INSERT 쿼리시 쓰기 지연은 동작하지 않는다

SEQUENCE

데이터베이스 시퀀스라는 기본 키를 할당하는 특별한 데이터 오브젝트를 사용하는 전략으로 ORACLE, PostgresSQL, DB2, H2데이터베이스에서 사용할 수 있다

CREATE TABLE BOARD (
  ID BIGINT NOT NULL PRIMARY KEY
  DATA VARCHAR(255)
)
// 시퀀스 생성
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;
@Entity
@SequenceGenerator(
  name = "BOARD_SEQ_GENERATOR",
  sequenceName = "BOARD_SEQ", // 매핑할 데이터베이스 시퀀스 이름
  initialValue = 1, allocationSize = 1
  )
public class Board {
  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE,
                  generator = "BOARD_SEQ_GENERATOR")
  private Long id;
}
  • SEQUENCE 사용 코드는 IDENTITY 전략과 같지만 내부 동작 방식에는 차이가 있다. SEQUENCE는 em.persist() 타이밍에 시퀀스를 사용해 식별자를 조회한다.

TABLE

키 생성 테이블을 이용, 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략이다. 모든 DB에 사용 가능하다 등의 대리키 전략이 있다

  • 테이블 전략을 위해서는 키 생성 용도로 사용할 테이블을 만들어야 한다.

create table MY_SEQUENCE (
  sequence_name varchar(255) not null,
  next_val bigint,
  primary key (sequence_name)
)

sequence_name 컬럼을 시퀀스 이름으로 사용하고 next_val 컬럼을 시퀀스 값으로 사용한다

@Entity
@TableGenerator(
  name = "BOARD_SEQ_GENERATOR",
  table = "MY_SEQUENCES",
  pkColumnValue = "BOARD_SEQ", allocationSize = 1
)
public class Board {
  @Id
  @GeneratedValue(strategy = GenerationType.TABLE,
                  generator = "BOARD_SEQ_GENERATOR")
  private Long id;
}
  • TABLE 전략은 시퀀스 대신에 테이블을 사용한다는 것만 제외하면 SEQUENCE 전략과 내부 동작방식이 같다.

@Embeddable

@Embedded

출처: 자바 ORM 표준 JPA 프로그래밍

Last updated