김영한님이 지음, [자바 ORM 표준 JPA 프로그래밍] 책을 읽고 정리한 필기입니다.📢

기본 키 매핑

1
2
3
4
5
6
7
@Entity
public class Member {
    @Id
    @Column(name = "ID")
    private String id;
  	...
}

기본 키를 데이터베이스가 생성해주는 값을 사용하려면 어떻게 매핑하는가?

ex) 오라클의 시퀀스 오브젝트, MySQL의 AUTO_INCREMENT

JPA의 기본 키 생성 전략

  • 직접 할당 : 기본 키를 애플리케이션에서 직접 할당, @Id 어노테이션만 명시
  • 자동 생성 : 대리 키 사용 방식, @Id + @GeneratedValue 명시
    • IDENTITY : 기본 키 생성을 데이터베이스에 위임, 데이터베이스 의존적
    • SEQUENCE : 데이터베이스 시퀀스를 사용해서 기본 키를 할당, 데이터베이스 의존적(오라클 O, MySQL X)
    • TABLE : 키 생성 테이블을 사용, 모든 데이터베이스에서 사용 가능

⚠️ 키 생성 전략을 사용하려면 hibernate.id.new_generator_mappings=true속성을 추가해야 함, 과거 호환성 유지하려고 기본값을 false로 두었다.

기본 키 직접 할당 전략

1
2
3
@Id
@Column(name = "id")
private String id;

@Id 어노테이션 적용 가능 자바 타입

  • 자바 기본형
  • 자바 래퍼(Wrapper)형
  • String
  • java.util.Date
  • java.sql.Date
  • java.math.BigDecimal
  • java.math.BigInteger

이 할당 전략은 em.persist()로 엔티티를 저장하기 전에 애플리케이션에서 기본 키를 직접 할당하는 방법

1
2
3
Board board = new Board();
board.setId("id1"); //기본 키 직접 할당
em.persist(board);

IDENTITY 전략

기본 키 생성을 데이터베이스에 위임하는 전략

  • 주로 사용하는 DB : MySQL, PostgreQSL, SQL Server ,DB2
  • JPA는 기본 키 값을 얻어오기 위해 데이터베이스를 추가로 조회

⚠️ 엔티티가 영속 상태가 되려면 식별자가 반드시 필요한데, 이 전략은 저장을 해야 식별자를 구할 수 있으므로 em.persist()를 호출하는 즉시 INSERT SQL이 실행된다. 때문에 이 전략은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다.

1
2
3
4
5
6
7
8
#IDENTITY 사용 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');
1
2
3
4
5
6
7
8
//IDENTITY 매핑 코드
@Entity
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    ...
}
1
2
3
4
5
6
//IDENTITY 사용 코드
private static void logic(EntityManager em){
    Board board = new Board();
    em.persist(board);
    System.out.println("board.id = " + board.getId()); //출력 : board.id = 1
}

💡 Tip. IDENTITY 전략과 최적화

IDENTITY 전략은 데이터를 데이터베이스에 INSERT한 후에 기본 키 값을 조회할 수 있다. 따라서 엔티티에 식별자 값을 할당하려면 JPA는 추가로 데이터베이스를 조회해야 한다. JDBC3에 추가된 Statement.getGeneratedKeys() 를 사용하면 데이터를 저장하면서 동시에 생성된 기본 키 값도 얻어올 수 있다. 하이버네이트는 이 메소드를 사용해 데이터베이스와 한 번만 통신한다.

SEQUENCE 전략

시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트, SEQUENCE 전략은 이 오브젝트를 사용

사용 가능한 DB : 오라클, PostgreSQL, DB2, H2

1
2
3
4
5
6
7
8
#SEQUENCE 사용 SQL
CREATE TABLE BOARD (
    ID BIGINT NOT NULL PRIMARY KEY,
    DATA VARCHAR(255)
)

#SEQUENCE 생성
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;
1
2
3
4
5
6
7
8
9
10
11
12
//시퀀스 매핑 코드
@Entity
@SequenceGenerator(
	name = "BOARD_SEQ_GENERATOR", 
	sequenceName = "BOARD_SEQ", //매핑할 데이터베이스 시퀀스 이름
  initalValue = 1, allocationSize = 1)
public class Board{
  	@Id
  	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "BOARD_SEQ_GENERATOR")
	  private Long id;
  	...
}
1
2
3
4
5
6
7
//시퀀스 사용 코드
private static void logic(EntityManager em){
    Board board = new Board();
    em.persist(board);
    Systme.out.println("board.id = " + board.getId());
}
//출력 : board.id = 1

IDENTITY와의 차이점

  • IDENTITY와 내부 동작 방식은 “반대”
    • IDENTITY : 엔티티 데이터베이스 저장 → 식별자 조회 (지연쓰기 기능 동작 X)
    • SEQUENCE : 식별자 조회 → 엔티티에 부여 후 데이터베이스 저장

@SequenceGenerator

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

⚠️ SequenceGenerator.allocationSize의 기본값이 50인 것에 주의해야 함. JPA가 기본으로 생성하는 데이터베이스 시퀀스를 1이므로 한번에 50씩 증가한다. 기본값이 50인 이유는 최적화 때문이다. 데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값을 반드시 1로 설정해야 한다.

💡 Tip. SEQUENCE 전략과 최적화

SEQUENCE 전략은 데이터베이스와 2번 통신한다.

  1. 식벽자를 구하기 위해 데이터베이스 시퀀스를 조회

    SELECT BOARD_SEQ.NETVAL FROM DUAL

  2. 조회한 시퀀스를 기본 키 값으로 사용해 데이터베이스에 저장

    INSERT INTO BOARD...

JPA는 시퀀스에 접근하는 횟수를 줄이기 위해 @SequenceGenerator.allocationSize를 사용하는데, 이 설정 만큼 한 번에 시퀀스 값을 증가시키고 나서 그만큼 메모리에 시퀀스 값을 할당하기 위해서이다. 예를 들어 allocationSize가 50이면 시퀀스를 한 번에 50증가 시킨 후 1~50까지는 메모리에서 식별자를 할당한다.

이 최적화 방법은 시퀀스 값을 선점하여 JVM이 동시에 동작해도 기본 키 값이 충돌하지 않는 장점이 있지만 시퀀스 값이 한번에 많이 증가하는 점을 염두해야 한다. 이런 상황이 부담스럽다면 1로 설정해야 한다.

그리고 앞서 설명한 hibernate.id.new_generator_mappings속성이 true가 아니라면 과거 사용하던 방법으로 키 생성을 최적화하므로 주의해야 한다.

과거 키 생성 방식 : 시퀀스 값을 하나씩 할당받고 애플리케이션에서 allocationSize만큼 사용, 50이라면 반환된 시퀀스 값이 1일 경우 애플리케이션에서 1~50까지 사용, 2일 경우 51~100까지 사용하는 방식

TABLE전략

테이블을 사용하는 전략이므로 모든 데이터베이스에 적용 가능하다.

1
2
3
4
5
6
#TABLE 전략  생성 DDL
CREATE TABLE MY_SEQUENCES(
    sequence_name varchar(255) not null,
    next_val bigint,
    primary key(sequence_name)
)
1
2
3
4
5
6
7
8
9
10
11
12
//TABLE 전략 매핑 코드
@Entity
@SequenceGenerator(
	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;
  	...
}
1
2
3
4
5
6
7
//TABLE 전략 매핑 사용 코드
private static void logic(EntityManager em){
    Board board = new Board();
    em.persist(board);
    Systme.out.println("board.id = " + board.getId());
}
//출력 : board.id = 1

TABLE 전략은 시퀀스 대신에 테이블을 사용한다는것만 제외하고 SEQUENCE 전략과 내부 동작방식이 동일

  • BOARD_SEQ 컬럼이 추가되어 관리됨
  • 테이블이 비어 있어도 JPA가 알아서 INSERT 하므로 따로 초기화 필요X

@TableGenerator

속성 기능 기본값
name 식별자 생성기 이름 필수
table 키생성 테이블명 hibernate_sequences
pkColumnName 시퀀스 컬럼명 sequence_name
valueCoumnName 시퀀스 값 컬럼명 next_val
pkCoumnValue 키로 사용할 값 이름 엔티티 이름
initialValue 초기 값, 마지막으로 생성된 값이 기준 0
allocationSize 시퀀스 한 번 호출에 증가하는 수, 성능 최적화에 사용됨 50
catalog, schema 데이터베이스 catalog, schema 이름  
uniqueConstraints(DDL) 유니크 제약 조건을 지정할 수 있음  

💡 Tip. TABLE 전략과 최적화

TABLE 전략은 값을 조회하면서 SELECT 쿼리를 사용하고 다음 값으로 증가시키기 위해 UPDATE 쿼리를 사용한다. 이 전략은 SEQUENCE 전략과 비교해서 데이터베이스와 한 번 더 통신하는 단점이 있다. TABLE 전략을 최적화하려면 @TableGenerator.allocationSize를 사용하면 된다. 이 값을 사용해서 최적화하는 방법은 SEQUENCE 전략과 같다.

AUTO 전략

선택된 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다.

1
2
3
4
5
6
7
@Entity
public class Board{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    ...
}

장점

  • 데이터베이스를 변경해도 코드를 수정할 필요 없음
  • 개발 초기 단계 프로토타입 개발 시 편리함

정리

  • 직접 할당 : em.persist() 를 호출하기 전에 애플리케이션에 직접 식별자 값을 할당해야 함, 그렇지 않다면 예외 발생
  • SEQUENCE : 데이터베이스 시퀀스에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장
  • TABLE : 데이터베이스 시퀀스 생성용 테이블에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장
  • IDENTITY : 데이터베이스에 엔티티를 저장해서 식별자 값을 획득한 후 영속성 컨텍스트에 저장

JPA 카테고리 내 다른 글 보러가기

댓글남기기