본문 바로가기
JPA/자바 ORM 표준 JPA 프로그래밍 - 기본편

[JPA] 기본 키 매핑

by hk27 2022. 2. 3.

안녕하세요.

기본 키 매핑을 알아보겠습니다.

 

지난 게시글에서 엔티티를 테이블에, 필드를 컬럼에 매핑하는 방법을 알아보았습니다. 

이어지는 내용이니 관심 있는 분들은 아래 게시글을 참고해주세요.

[JPA] 엔티티 매핑

 

기본 키 매핑

기본 키(PK, Primary Key)는 관계형 DB의 식별자입니다. 

모든 엔티티는 고유의 기본 키를 가져야 합니다.

JPA는 '@ID' 어노테이션으로 기본 키 매핑을 지원합니다.

기본 키로 사용할 필드에 @ID 어노테이션을 붙이면 됩니다.

@Entity
public class Member {

    @Id
    private Long id;
    private String username;
    private Integer age;
}

@Id 어노테이션만 사용하면 영속성 컨텍스트에 persist 하기 전에 id 값을 직접 지정해줘야 합니다. 

이를 직접 할당 방식이라고 합니다. 

그러나 보통 DB를 관리할 때 id값은 1, 2, 3, 4 ... ... 로 자동으로 생성되게 설정하는 경우가 대부분이고, 이를 자동 생성 방식이라고 합니다.

자동 생성 방식을 사용하려면 기본 키 필드에 @GeneratedValue를 지정하면 됩니다. 

 

자동 생성

@Entity
public class Member {
    
    @Id @GeneratedValue
    private Long id;
    private String username;
    private Integer age;
}

 

그렇다면 자동으로 어떤 id가 생성될까요? 이는 @GeneratedValue의 속성값에 따라 다릅니다.

1. IDENTITY: 기본 키 생성을 DB에 위임합니다. MYSQL 사용 시 이용합니다. 

2. SEQUECE: DB 시퀀스를 사용해서 기본 키를 할당합니다. @SequenceGenerator가 필요하고, ORACLE 사용 시 이용합니다.

3. TABLE: 키 생성 테이블을 사용해서 기본 키를 할당합니다. @TableGenerator가 필요하고, 모든 DB에 사용할 수 있습니다. 

4. AUTO: 사용하는 DB에 따라 위의 3가지 방법 중 하나를 자동으로 설정합니다. 기본값입니다. 

 

하나씩 자세히 알아봅시다. 

 

1. IDENTITY

기본 키 생성을 데이터베이스에 완전히 위임하는 전략입니다.

주로 MySQL, PostgreSQL, Server, DB2에서 사용합니다.

예를 들어서 MySQL에서 IDENTITY 방법을 사용하면 MySQL의 AUTO_INCREMENT 기능을 사용하며, DB가 기본 키를 자동으로 생성해줍니다.

MySQL의 AUTO_INCREMENT 예제는 다음과 같습니다.

 

SQL 쿼리

CREATE TABLE BOARD (
    ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    DATA VARCHAR(255)
);

INSERT INTO BOARD(DATA) VALUES('A');
INSERT INTO BOARD(DATA) VALUES('B');

테이블을 생성할 때 기본 키 컬럼에 AUTO_INCREMENT를 추가합니다. 

데이터 두 개를 넣으면 ID가 한 개씩 자동으로 올라가며 저장됩니다.

 

BOARD 테이블 결과

ID DATA
1 A
2 B

 

JPA 코드를 아래처럼 작성하면 위의 DDL과 같은 결과를 만들 수 있습니다. 

@Entity
public class Board {
    @Id @GeneratedValue(strategy = IDENTITY)
    @Column(name="ID")
    private Long id;
    @Column(name="DATA")
    private String data;
}

 

아래 코드는 어떻게 동작할까요?

Board board = new Board();
em.persist(board);
System.out.println("board.id = " + board.getId()); // board.id = 1

결과는 id가 1로 잘 출력됩니다.

JPA 입장에서는 기본 키 설정을 DB에 위임하기 때문에 id를 바로 알지 못할 것 같지만 잘 동작하였습니다. 

board 객체를 영속성 컨텍스트에 저장하기 위해서는 id 값이 필요하기 때문에 JPA는 바로 INSERT 쿼리를 전송하여 DB에 엔티티를 저장하고 id 값을 받아옵니다. 

원래 영속성 컨텍스트는 트랜잭션을 지원하는 쓰기 지연이 동작해 INSERT 쿼리가 플러시 시점에 한 번에 전달되지만, 이 경우는 그렇지 않고 바로 전달됩니다. 

 

2. SEQUENCE

데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트입니다.

SEQUENCE 전략은 이 시퀀스를 사용해서 기본 키를 생성합니다.

이 전략은 시퀀스를 지원하는 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용할 수 있습니다.

 

아래 SQL과 같이 시퀀스를 생성해야 합니다.

CREATE TABLE BOARD (
    ID BIGINT NOT NULL PRIMARY KEY,
    DATA VARCHAR(255)
)

//시퀀스 생성
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;

 

엔티티 코드입니다. 

@Getter @Setter
@Entity
@SequenceGenerator(
        name = "BOARD_SEQ_GENERATOR", // 시퀀스 생성기 이름
        sequenceName = "BOARD_SEQ", // 실제 DB의 시퀀스
        initialValue = 1, allocationSize = 1)
public class Board {
    @Id
    @GeneratedValue(strategy = SEQUENCE, generator = "BOARD_SEQ_GENERATOR") // 방금 등록한 시퀀스 생성기 선택
    // 식별자 값은 BOARD_SEQ_GENERATOR 시퀀스 생성기가 할당함
    private Long id;
}

@SequenceGenerator를 통해 시퀀스 생성기를 만들고, 실제 db의 시퀀스와 매핑합니다.

@GeneratedValue에서 전략을 SEQUENCE로 설정하고, generator로 위에서 어노테이션으로 설정한 시퀀스 생성기 이름을 선택합니다.

 

DB를 수행해보면 아래 사진처럼 BOARD_SEQ가 만들어진 것을 확인할 수 있습니다. 

 

@SequenceGenerator의 속성을 정리해봅시다.

속성 기능 기본값
name 식별자 생성기 이름 필수
sequenceName 데이터베이스에 등록된 시퀀스 이름 hibernate_sequence
initialValue DDL 생성 시에만 사용됨. 시퀀스 DDL을 생성할 때 처음 시작하는 수 지정 1
allocationSize 시퀀스 한 번 호출에 증가하는 수
(성능 최적화에 이용)
50
catalog, schema 데이터베이스 catalog, schema 이름  

매핑할 DDL은 다음과 같습니다.

CREATE SEQUENCE [sequenceName]
START WITH [initialValue] INCREMENT BY [allocationSize]

 

allocationSize의 기본값이 50이라는 것에 주의해야 합니다.

한 번 시퀀스를 호출할 때마다 50개씩 id를 할당해놓는다는 것입니다. 

매번 시퀀스값을 확인하기 위해서 DB에 접근하면 네트워크를 타고 성능이 저하될 수 있기 때문에, 성능 최적화를 위해서 50개를 한 번에 할당합니다. 

 

allocationSize에 대한 내용은 분량이 많아서 게시글을 따로 분리했습니다.

관심 있는 분들은 아래 게시글을 참고해주세요.

시퀀스 allocationSize 정리

 

3. TABLE

TABLE 전은 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내 내는 전략입니다.

이 전략은 테이블을 만들어 사용하면 되므로 모든 DB에 적용할 수 있다는 장점이 있습니다.

 

TABLE 전략을 사용하려면 아래 DDL처럼 키 생성 용도로 사용할 테이블을 만들어야 합니다.

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

sequence_name 컬럼이 시퀀스의 이름이고, next_val 컬럼이 시퀀스값입니다.

 

엔티티 코드입니다. 

@Getter @Setter
@Entity
@TableGenerator(
        name = "BOARD_SEQ_GENERATOR",
        table = "MY_SEQUENCES",
        pkColumnValue = "BOARD_SEQ", allocationSize = 1)
public class Board {
    
    @Id @GeneratedValue(strategy = TABLE, generator = "BOARD_SEQ_GENERATOR")
    @Column(name="ID")
    private Long id;
}

 

MY_SEQUENCES 테이블이 자동으로 생성되고, BOARD_SEQ 데이터가 자동으로 들어갑니다. 

 

Board 객체를 테이블에 넣어봅시다. 

Board board = new Board();
System.out.println("================================");
em.persist(board);
System.out.println("================================");
System.out.println("board.id = " + board.getId()); // board.id = 1

 

수행 결과

================================
SELECT from MY_SEQUENCES ***

UPDATE MY_SEQUENCES ***
================================
board.id = 1
INSERT INTO Board

 

TABLE 전략은 시퀀스값을 찾기 위해서 SELECT 쿼리를 사용하고 값을 증가시키기 위해서 UPDATE 쿼리를 사용합니다. 이 전략은 SEQUENCE 전략보다 DB와 한 번 더 통신한다는 단점이 있기 때문에, allocationSize를 사용해서 최적화할 수 있습니다. SEQUENCE 전략과 마찬가지로 TABLE 전략의 allocationSize 기본값도 50입니다.

 

allocationSize를 50으로 설정하고 board 객체 3개를 DB에 저장하는 코드를 수행해봅시다.  

        Board board1 = new Board();
        System.out.println("================================");
        em.persist(board1);
        System.out.println("================================");
        System.out.println("board.id = " + board1.getId());

        Board board2 = new Board();
        System.out.println("================================");
        em.persist(board2);
        System.out.println("================================");
        System.out.println("board.id = " + board2.getId());

        Board board3 = new Board();
        System.out.println("================================");
        em.persist(board3);
        System.out.println("================================");
        System.out.println("board.id = " + board3.getId());

 

결과는 아래와 같습니다. 

================================
select from MY_SEQUENCES
update MY_SEQUENCES 
select from MY_SEQUENCES
update MY_SEQUENCES 
================================
board.id = 1
================================
================================
board.id = 2
================================
================================
board.id = 3
insert into Board
insert into Board
insert into Board

처음에만 select, update 문을 보내고 다음부터는 메모리에 저장된 값을 사용합니다. 

 

 

4. AUTO

@GeneratedValue의 기본 전략은 AUTO입니다.

단어 그대로 자동 방식으로, 선택한 DB 방언에 따라서 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택합니다.

예를 들어서 MySQL을 사용하면 IDENTITY를, 오라클을 사용하면 SEQUENCE를 선택합니다. 

 

엔티티 코드는 아래와 같습니다.

@Entity
public class Board {
    @Id
    @GeneratedValue //(strategy = AUTO)
    private Long id;
}

 

만약 SEQUENCE나 TABLE 전략이 선택되면 시퀀스나 키 생성용 테이블을 미리 만들어 두어야 합니다.

만약 스키마 자동 생성 기능을 사용한다면 하이버네이트가 기본값을 사용해서 적절한 시퀀스나 키 생성용 테이블을 만듭니다. 

 

 

인프런  '자바 ORM 표준 JPA 프로그래밍 - 기본편' 강의를 듣고 공부하며 정리한 자료입니다. 

잘못된 부분은 피드백 주시면 감사하겠습니다. 

글 읽어주셔서 감사합니다 :-)

 

참고 자료

자바 ORM 표준 JPA 프로그래밍 - 기본편 섹션 4. 엔티티 매핑, https://www.inflearn.com/course/ORM-JPA-Basic

김영한, 자바 ORM 표준 JPA 프로그래밍, 에이콘출판(2015), pp.131-142.

 

 

댓글