본문 바로가기
Back-end/JPA 개념

6. Primary key(기본 키) Mapping

by devraphy 2022. 3. 29.

0. 개요

- 이전 포스팅에서 Field와 Column을 매핑하는 방법을 배웠다.

- 이번 포스팅에서는 DB에 존재하는 기본 키(= Primary key, PK)를 매핑하는 방법에 대해서 배워보자.

 

1. Annotation

 - PK 매핑에 필요한 Annotation을 알아보자.

 

a) @Id

- DB의 PK를 명시하기 위해서, Entity의 field에 @Id를 부착한다.

- @Id는 PK의 값을 직접 할당하는 경우에 사용한다.

@Entity
public class Member {

    @Id
    private Long id;

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

}

 

b) @GeneratedValue

- PK의 값을 직접 할당하지 않고 DB에게 값 할당의 권한을 넘기는 경우 사용된다.

- 즉, DB가 자동으로 생성해주는 숫자를 사용한다.

 

- @GeneratedValue의 strategy 옵션은 다음과 같은 값을 설정할 수 있다.

  → IDENTITY

  → SEQUENCE

  → TABLE

  → AUTO(= default 설정)

 

- 모든 옵션은 persistence.xml에 설정한 DB 방언에 따라 알맞은 SQL로 변환되어 적용된다.

- 각 strategy에 대해서 알아보자.

 

2. GenerationType.IDENTITY

- PK 값의 생성을 DB에게 위임한다.

- JPA에서 id 필드의 값에 아무것도 입력하지 않고 그냥 null로 넘기면, DB에서 PK 값을 생성 및 할당한다.

- 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용한다. ex) MySQL의 AUTO_INCREMENT 

@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
}

 

a) IDENTITY 전략의 문제점

- IDENTITY를 사용하면, JPA에서 PK의 값을 Null로 넘긴다. 그러면 DB에서 PK 값을 생성하고 할당한다.

- 즉, DB에 값을 주입하기 전까지 JPA의 Persistence Context는 입력된 PK 값을 알 수 없다.

- 이렇게 되면 Persistence Context의 1차 캐시를 사용할 수 없게 된다. 

- 어떻게 해야 할까? 

 

b) IDENTITY 문제의 해결책

- IDENTITY를 사용하면 JPA에서 쿼리를 전달하는 시점이 달라진다.

- 기존의 동작 방식은 commit() 시점에서 쓰기 지연 SQL 저장소에 보관된 SQL을 일괄 처리했다. 

 

- 그러나 IDENTITY를 사용하면 em.persist()를 호출할 때 DB에게 쿼리를 전달한다.

- DB에 데이터를 먼저 입력시킨 다음, JPA에서 SELECT 쿼리를 전달하여 해당 PK의 값을 가져온다.

- IDENTITY를 사용하면 이와 같은 동작 흐름을 가지게 된다.

 

3. GenerationType.SEQUENCE

- SEQUENCE는 unique 한 값을 순서대로 생성하는 DB object이다.

- 시퀀스 값이 PK에 순차적으로 할당된다. 

 

- SQUENCE를 사용할 때에는 반드시 Entity 필드의 자료형을 숫자형으로 사용해야 한다.

- 그중에서도 반드시 Long을 사용하는 것을 권장한다. (표현 가능한 숫자의 범위가 가장 크기 때문이다.)

 

- 주로 Oracle에서 사용하는 방식이다.

@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    
}

 

a) @SequenceGenerator

- 만약 테이블마다 SEQUENCE를 분리하여 관리하고 싶다면, @SequenceGenerator를 사용하면 된다.

- SEQUENCE를 사용할 때에는 @SequenceGenerator를 통해 SEQUENCE를 설정할 수 있다.

 

→ name 속성(필수 값)

    - @SequenceGenerator의 이름을 설정한다.

 

→ sequenceName 속성(default = hibernate_sequences)

   - 매핑할 DB의 SEQUENCE 객체의 이름을 설정한다.

 

→ initialValue 속성(defualt = 1)

   - 초기 값(= 시작 값)을 설정한다.

 

→ allocateSize 속성(default = 50) 

   - DB로부터 SEQUENCE 값을 가져올 때, 한 번에 몇 개의 SEQUENCE를 가져올 것인지 설정한다.

   - JPA 메모리에 미리 SEQUENCE 값을 저장시켜놓고 사용한다.

   - allocationSize를 1로 설정하면, em.persist()가 한번 호출될 때마다 한 개의 SEQUENCE를 DB로부터 받아온다.

   - 그러므로 매번 em.persist()를 호출할 때마다 DB와의 네트워킹이 발생한다.

   - 이와 같이 낭비되는 네트워킹을 방지하기 위해서 적정한 allocationSize를 사용한다.

* 다수의 서버가 동일한 DB를 사용하더라도, allocationSize로 인한 동시성 문제는 발생하지 않는다.

 

→ catalog와 schema 속성

   - 두 옵션은 DB의 catalog와 schema 이름을 설정한다.

 

@Entity
@SequenceGenerator(name = "MEMBER_SEQ_GEN", sequenceName = "MEMBER_SEQ",
                   initialValue = 1, allocationSize = 1)
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "MEMBER_SEQ_GEN")
    private Long id;
}

 

- @GeneratedValue의 generator 옵션에는 사용할 시퀀스의 이름을 설정한다.

- 사용할 시퀀스의 이름이란, @SequenceGenerator의 name 값을 말한다.

 

b) SEQUENCE 호출 시점

- DB에 데이터를 저장하기 위해서는 em.persist()를 호출해야 한다.

- 그러나 SEQUENCE를 사용하기 때문에, JPA는 PK 값을 알지 못한다.

- 그러므로 em.persist()를 호출되면 JPA가  내부적으로 DB로부터 SEQUENCE 값을 가져온다.

 

4. GenerationType.TABLE

- key 생성을 위한 전용 테이블을 만드는 기능이다.

- 모든 DB에 적용할 수 있다는 것이 장점이다.

- 숫자를 생성하는 측면에서 최적화가 되어있지 않기 때문에 성능이 떨어진다는 단점이 있다.

- 그러므로 운영 서버에서는 잘 사용하지 않는 전략이다.

 

a) @TableGenerator

- TABLE 전략을 사용할 때에는 @TableGenerator와 함께 사용할 수 있다.

- @TableGenerator를 사용하면 key 생성을 위한 테이블을 설정할 수 있다.

 

→ name 속성(필수 값)

   - @TableGenerator의 이름을 설정한다.

 

→ table 속성(default = hibernate_sequences)

   - DB의 key 생성 테이블 이름을 설정한다.

 

→ pkColumnName 속성(default = sequence_name)

   - 시퀀스 칼럼 이름을 설정한다.

 

→ valueColumnNa 속성(default = next_val)

   - 시퀀스 값의 칼럼 이름을 설정한다.

 

→ pkColumnValue 속성(default = Entity 이름)

   - key로 사용할 칼럼의 이름을 설정한다.

 

→ initialValue 속성(default = 0)

   - 초기 값, 마지막으로 생성된 값을 기준으로 다음 값을 생성한다.

 

→ allocationSize 속성(default = 50)

   - DB로부터 SEQUENCE 값을 가져올 때, 한 번에 몇 개의 SEQUENCE를 가져올 것인지 설정한다.

 

→ catalog, schema 속성

   - DB의 catalog, schema 이름을 설정한다.

 

→ uniqueConstraints(DDL)

   - 유니크 제약조건을 설정한다.

@Entity
@TableGenerator(
          name = "MEMBER_SEQ_TAB",
          table = "TABLE_SEQ",
          pkColumnValue = "MEMBER_SEQ",
          allocationSize = 1)
public class Member {

    @Id
    @GeneratedValue(
                strategy = GenerationType.TABLE,
                generator = "MEMBER_SEQ_TAB")
    private Long id;
    
}

 

5. 권장하는 전략

- 여기까지 PK를 설정하고, 매핑하는 방법에 대해서 알아보았다.

- 그렇다면 IDENTITY, SEQUENCE, TABLE 중 무엇을 사용해야 할까?

- 사실 여기에 정해진 답은 존재하지 않는다. 다만, 다음의 요소를 고려해볼 필요가 있다.

 

a) PK 제약 조건

- PK는 null 값을 허용하지 않으며, 중복이 없어야 하고(= unique), 값이 변하면 안 된다.

- 이 3가지 조건을 만족해야지만 비로소 PK로 사용할 수 있다.

 

b) 불변성을 만족하는 방법

- PK 제약조건 중 가장 만족하기 어려운 것이 불변성이다.

- 존재하는 데이터의 PK는 서비스가 종료되기 전까지 변하면 안 되기 때문이다.

- 그러므로 PK는 비즈니스와 연관관계가 없는 랜덤 한 수를 사용하는 것을 권장한다.

  → PK = Long 타입 + 대체 key + 키 생성 전략 사용

'Back-end > JPA 개념' 카테고리의 다른 글

8. 양방향 연관 관계의 기본 개념  (0) 2022.03.31
7. 단방향 연관 관계  (0) 2022.03.30
5. Field(칼럼) Mapping  (0) 2022.03.28
4. Entity Mapping  (0) 2022.03.25
3. JPA의 내부구조와 동작  (0) 2022.03.24

댓글