4장. 엔티티 매핑

  • JPA에서 지원하는 mapping annotation은 크게 4가지로 분류 가능
  • 대표적 annotation은 아래와 같음
    1. 객체와 table mapping: @Entity, @Table
    2. primary key mapping: @Id
    3. field와 column mapping: @Column
    4. relational mapping: @ManyToOne, @JoinColumn
  • mapping은 xml로도 기술 가능




4.1 @Entity

  • table과 mapping할 class는 @Entity annotation 필수
  • 기본 생성자 필수
  • final class, inner class, enum, interface 에는 사용 불가
  • 저장할 field에 final 사용 불가
속성 기능 기본값
name JPA 에서 사용할 entity 이름을 지정 class의 이름




4.2 @Table

  • entity와 mapping할 table을 지정
속성 기능 기본값
name mapping할 table 이름 entity 이름
catalog catalog 기능이 있는 DB에서 catalog mapping  
schema schema 기능이 있는 DB에서 schema mapping  
uniqueConstraints (DDL) DDL 생성시 unique 제약조건 만듬.
2개 이상 복합 가능.
schema 자동 생성 기능 사용하여 DDL 만들 시에만 사용됨
 




4.3 다양한 mapping 사용

코드
@Entity                 // name = Member (default)
@Table(name="MEMBER")   // MEMBER table과 Member entity를 mapping
public class Member{
    @Id
    @Column(name = "ID")
    private String id;

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

    private Integer age;

    // enum 사용하려면 @Enumerated annotation으로 mapping
    @Enumerated(EnumType.STRING)
    private RoleType roleType;

    // 날짜 타입은 @Temporal annotation으로 mapping
    @Temporal(TemporalType.TIMESTAMP)
    private Date createdDate;

    // 길이제한 없는 CLOB 타입으로 저장하려면 @Lob annotation으로 mapping
    @Lob
    private String description;

    // Getter, Setter
    ...
}

...

public enum RoleType {
    ADMIN, USER
}

이름 mapping strategy 변경

  • 카멜 표기법(자바 entity)와 스네이크 표기법(DB column)을 @Column.name 속성 명시 없이 mapping하려면 hibernate.ejb.naming_strategy 속성 사용해서 이름 mapping strategy 변경하면 됨
  • hibernate 에서는 org.hibernate.cfg.ImprovedNamingStrategy class를 제공
  • 다음과 같이 적용 가능 <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy />

</div> </details>




4.4 DB schema 자동 생성

  • JPA는 DB schema 자동 생성 기능 지원
  • class의 mapping 정보를 보고 DB schema를 자동으로 생성해 줌
  • 이 기능을 사용하려면 persistence.xml에 다음과 같은 속성 추가해야 함
    • <property name="hibernate.hbm2ddl.auto" value="create" />

</div> </details>

  • 실제 실행되는 DDL 콘솔로 확인하려면 다음과 같은 속성 추가
    • <property name="hibernate.show_sql" value="true" />

</div> </details>

hibernate.hbm2ddl.auto

  • schema 자동생성 기능 사용 안하려면 이 속성을 아예 지우거나 유효하지 않은 옵션 주면 됨 (ex. none)
  • 유효한 옵션은 다음과 같음
옵션 설명
create 기존 table 삭제 후 생성 (DROP + CREATE)
create-drop create 속성에 추가로 애플리케이션 종료시 생성한 DDL 제거 (DROP + CREATE + DROP)
update DB table과 entity mapping 정보 비교하여 변경사항만 수정 (UPDATE)
validate DB table과 entity mapping 정보를 비교해서 차이 있으면 경고 남기고 어플리케이션 실행 안함



4.5 DDL 생성 기능

  • DDL 자동 생성 기능 사용시 @Column, @Table annotation의 속성을 활용하여 조건들을 추가할 수 있음
    • not null, unique 제약조건, varchar 길이 등등
  • !! 이러한 속성들은 자동 생성된 DDL에만 영향을 주며, 자동 생성 기능 미사용 시에는 아무런 역할도 하지 않음 !!

not null, length constraint

  • @Column.nullable 속성 값 false로 지정하여 자동 생성 DDL에 not null 제약조건 추가
  • @Column.length 속성 값을 지정하여 자동 생성 DDL에 문자의 크기 지정
코드
...
@Column(name = "NAME", nullable = false, length = 10)
private String username;
/** 이렇게 옵션 줄 경우 다음과 같이 DDL 생성됨
  * 
  * CREATE TABLE MEMBER (
  * ...
  *     NAME VARCHAR(10) NOT NULL,
  * ...
  * )
  * 
  */
...

unique constraint

  • @Table.uniqueConstraints 속성에 @uniqueConstraint annotation으로 유니크 제약조건들 추가
코드
@Entity(name="Member")
@Table(name="MEMBER", uniqueConstraints = {@UniqueConstraint(
    name = "NAME_AGE_UNIQUE",
    columnNames = {"name", "age"} 
)})
public class Member {
...
    @Column(name = "name")
    private String username;

    private Integer age;
/** 이렇게 옵션 줄 경우 다음과 같이 DDL 생성됨
  * 
  * ALTER TABLE MEMBER 
  *     ADD CONSTRAINT NAME_AGE_UNIQUE UNIQUE (name, age)
  * 
  */
...
}




4.6 primary key mapping

  • JPA에서 제공하는 DB primary key 생성 strategy
    1. IDENTITY: primary key 생성을 DB에 위임
    2. SEQUENCE: DB sequence를 사용해서 할당
    3. TABLE: 키 생성 table을 사용
  • 오라클은 squence를 제공하지만 mysql은 X, 대신 auto_increment 제공
    • ∴ SEQUNCE, IDENTITY strategy은 사용하는 DB에 의존
  • TABLE strategy은 키 생성용 table을 만들어두고 sequence 처럼 활용하는 거라 DB 의존 X
  • primary key 자동 생성 사용하려면 @Id에 @GeneratedValue annotation 추가하고 원하는 키 생성 strategy 선택하면 됨
    • 먼저 아래와 같이 persistence.xml에 키 생성 strategy 속성 추가해야 함
      
      

</div> </details>



4.6.1 primary key 직접 할당 strategy

  • 그냥 @Id annotation으로 mapping하면 됨
  • @Id 적용 가능 자바 타입
    • 자바 기본 타입
    • 자바 Wrapper 타입
    • String
    • java.util.Date
    • java.sqlDate
    • java.math.BigDecimal
    • java.math.BigInteger
코드
@Entity
@Table(name="member")
public class Member {
@Id
private String id;
...
}

...

Member member = new Member();

// entity 저장 전에 직접 키 할당
member.setId("id1");
em.persist(member);



4.6.2 IDENTITY strategy

  • primary key 생성을 DB에 위임
  • MySQL, PostgreSQL, SQL Server, DB2 에서 주로 사용
  • IDENTITY strategy은 mySQL AUTO_INCREMENT 처럼 DB 값 저장 후에야 primary key 구할 수 있을 때 사용
  • @GeneratedValue(strategy = GenrationType.IDENTITY) annotation 추가하면 DB 추가로 조회해서 primary key값 얻어옴
    • em.persit() 호출해서 entity 저장하는 시점에 DB가 생성한 값을 JPA 가 추가적으로 조회함 (별도 쿼리)
코드
// code
@Entity
@Table(name="MEMBER")
public class Member {
    @Id
    @GeneratedValue(strategy = GenrationType.IDENTITY)
    private int id;
    @Column(length = 64)
    private String name;
...
}

sql
# sql
CREATE TABLE MEMBER (
    ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    NAME VARCHAR(64) 
...
);

...
# ID column 비워두면 DB 순서대로  만들어서 넣어줌
INSERT INTO MEMBER(NAME) VALUES('Alice');   // ID = 1
INSERT INTO MEMBER(NAME) VALUES('Bob');     // ID = 2

IDENTITY strategy과 최적화
JDBC3에 추가된 ```Statement.getGeneratedKeys() 를 사용하면 추가 조회 없이 데이터 저장과 동시에 생성된 primary key값 조회 가능

IDENTITY strategy 사용시 쓰기 지연
IDENTITY 사용시 entity를 DB에 저장해야 identifier 구할 수 있기 때문에 em.persist() 호출하면 쓰기 지연 없이 바로 INSERT 쿼리 전송됨



4.6.3 SEQUENCE strategy

  • DB sequnce는 유니크한 값을 순차로 생성하는 특별한 DB object
  • SEQUENCE strategy은 DB sequence 사용해서 primary key 생성
  • 오라클, PostgreSQL, DB2, H2 에서 사용 가능
  • SEQUENCE strategy을 사용하려면 먼저 DB에서 sequence 생성해줘야 함 (DDL 자동생성 기능 사용해도 됨)
  • SEQUENCE 사용하면 em.persist() 호출시 DB sequence 사용해서 identifier 조회 후 entity에 할당 (flush 전까지 DB 저장 X)
    • ∴ IDENTITY와 달리 쓰기 지연 가능
sql
# sql
CREATE TABLE MEMBER (
    ID INT NOT NULL PRIMARY KEY,
    NAME VARCHAR(64)
)

# sequence 생성
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;

코드
// code

// entity에 sequence mapping
@Entity
@SequenceGenerator(
    name = "BOARD_SEQ_GENERATOR"    // SequenceGenerator 이름
    sequenceName = "BAORD_SEQ",     // mapping할 DB sequence 이름
    initalValue = 1, allocationSize = 1
)
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "BOARD_SEQ_GENERATOR")  // SequenceGenerator mapping
    // @SequenceGenrator(...)                           // @SequenceGenerator는 여기에다 붙여도 됨
    private int id;
}


@SequenceGenerator

속성 기능 기본값 비고
name identifier 생성기 이름 필수값  
sequenceName DB에 등록되어 있는 sequence 이름 hibernate_sequence sequenceName 기본값은 구현체별로 다름
initalValue DDL 생성시에만 사용됨. sequence DDL 생성할 때 처음 시작하는 수 지정 1  
allocationSize sequence 한번 호출에 증가하는 수 50 성능 최적화에 사용. 반드시 DB sequnce allocationSize에 설정된 값과 똑같이 설정해줘야 함
catalog DB catalog 이름    
schema DB schema 이름    
  • mapping되는 DDL은 다음과 같음
sql
    CREATE SEQUENCE [sequenceName]
    START WITH [initialValue] INCREMENT BY [allocationSize]
    

SEQUENCE strategy과 최적화
SEQUENCE strategy 사용시 DB sequence 에서 할당해줄 identifier을 조회해야 하기 때문에 DB와 2번 통신함

  1. identifier 구하려고 DB sequence 조회
    SELECT BOARD_SEQ.NEXTVAL FROM DUAL
  2. flush 할 때 조회한 sequence를 primary key 값으로 entity를 DB에 저장
    INSERT INTO BOARD...
    따라서 sequence 접근 횟수를 줄이기 위해 @SequenceGenerator.allocationSize 속성 사용

    hibernate.id.new_generator_mappings=true 일 경우
    allocationSize 값 만큼 한번에 DB sequence 증가시키고 그만큼 메모리에 sequence 값 할당해서 사용
    다 쓰면 다시 DB sequence 조회하고 메모리 할당 반복

    hibernate.id.new_generator_mappings=false 일 경우
    DB sequence는 하나씩 증가시키고 대신 애플리케이션에서는 allocationSize 크기만큼 사용
    ex) allocationSize=50인 경우에 반환된 sequence 값이 1이면 1~50 사용, 2이면 51~100 사용하는 방식



4.6.4 TABLE strategy

  • 키 생성 전용 table을 하나 만들어 DB sequence 를 흉내내는 strategy
  • table을 사용하기 때문에 모든 DB에 사용 가능
  • 먼저 키 생성 용도 table 만들어야 함 (DDL 자동생성 기능 사용해도 됨)
    • table에 값 없으면 JPA가 알아서 초기화 하기 때문에 미리 넣어둘 필요 X
  • DB sequence 대신 table 사용한다는 점 빼면 내부 동작 방식은 SEQUENCE strategy과 동일
sql
# sql
CREATE TABLE MY_SEQUENCES (
    sequence_name VARCHAR(255) NOT NULL,    // sequence 이름
    next_Val bigint,                        // sequence 
    primary key ( sequence_name )
)

코드
// code
@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 int id;
}


@TableGenerator

속성 기능 기본값 비고
name identifier 생성기 이름 필수값  
table 키생성 table명 hibernate_sequences 하이버네이트 기준
pkColumnName sequence column명 sequence_name 하이버네이트 기준
valueColumnName sequence 값 column명 next_val 하이버네이트 기준
pkColumnValue 키로 사용할 값 이름 entity 이름  
initalValue 초기 값. 마지막 생성된 값이 기준 0  
allocationSize sequence 한번 호출에 증가하는 수 50 성능 최적화에 사용
schema DB schema 이름    
uniqueConstratins 유니크 제약조건 지정   DDL

TABLE strategy과 최적화
최적화 하기 위해서는 SEQUENCE 와 마찬가지로 @TableGenerator.allocationSize 를 사용
다만 TABLE strategy은 SELECT 쿼리로 sequence 값 조회 후 sequence 증가시키기 위해 UPDATE 쿼리를 사용해서 최적화 해도 SEQUENCE strategy보다 한 번 더 DB 와 통신하게 됨



4.6.5 AUTO strategy

  • 선택한 DB에 따라 IDENTITY, SEQUENCE, TABLE strategy 중 하나를 자동으로 선택
    • ex. 오라클: SEQUENCE, MySQL: IDENTITY
  • AUTO에 의해 SEQUENCE, TABLE strategy 선택될 경우 sequence 혹은 키 생성용 table을 미리 만들어 두어야 함
    • 아니면 schema 자동 생성 기능 써서 기본값으로 자동 생성되도록 하면 됨



4.6.6 primary key mapping 정리

  • persistence context는 identifier으로 entity 구분
  • ∴ entity를 persist 상태로 만들려면 identifier 필수
  • em.persist() 호출 직후 primary key mapping strategy 별로 아래와 같이 진행됨
    • 직접 할당: em.persist() 호출 전에 identifier 할당 해놔야 함
    • IDENTITY: DB에 entity 먼저 저장해서 identifier 획득 후 persistence context에 저장
    • SEQUENCE: DB sequence에서 identifier 획득 후 persistence context에 저장
    • TABLE: DB sequence 생성용 table에서 identifier 획득 후 persistence context에 저장