개발서적/자바 ORM 표준 JPA

[자바 ORM 표준 JPA 프로그래밍] 4.6 기본 키 매핑

Reference. 자바 ORM 표준 JPA 프로그래밍

책 목차 및 이전 글

@Entity
public class Member {
	@Id
	@Column (name = "ID")
	private String id;
	...
}
  • JPA가 제공하는 데이터베이스 기본 키 생성 전략
    • 직접 할당: 기본 키를 애플리케이션에서 직접 할당
    • 자동 생성: 대리키 사용 방식
      • IDENTITY: 기본키 생성을 데이터베이스에 위임
      • SEQUENCE: 데이터베이스 시퀀스를 사용해서 기본 키를 할당
      • TABLE: 키 생성 테이블을 사용
  • 자동 생성 전략이 다양한 이유는 데이터베이스 벤더마다 지원 방식이 다름 → 오라클은 시퀀스를 제공, MySQL은 시퀀스 X
  • TABLE 전략은 키 생성 테이블을 만들어 시퀀스처럼 사용하는 방식 → 모든 데이터베이스에서 사용이 가능
  • 자동 생성 전략을 사용하려면 @GeneratedValue를 추가 후 생성 전략을 선택
@Entity
public class Board {
		@Id
		@GeneratedValue(strategy = GenerationType.IDENTITY)
		private Long id;
		...
}

<주의>

키 생성 전략을 사용하려면 persistence.xmlhibernate.id.new_generator_mappings 속성 추가

<property name="hibernate.id.new_generator_mappings" value="true" />

4.6.1 기본 키 직접 할당 전략

Board board = new Board();
board.setId("id1"); //기본 키 직접 할당
em.persist(board);
  • @Id 적용 가능 자바 타입
    • 자바 기본형
    • 자바 래퍼(Wrapper) 형
    • String
    • java.util.Date
    • java.sql.Date
    • java.math.BigDecimal
    • java.math.BigInteger
  • 기본 키 직접할당 전략에서 식별자 값이 없으면 예외 발생 → 하이버네이트 구현체를 사용할 경우 javax.persistence.PersistenceException 발생

4.6.2 IDENTITY 전략

  • 기본키 생성을 데이터베이스에 위임하는 전략
  • 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용
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');

결과 값

IDDATA
1A
2B
  • IDENTITY 전략을 사용하려면 @GeneratedValue(strategy = GenerationType.IDENTITY)로 지정
@Entity
public class Board {
		@Id
		@GeneratedValue(strategy = GenerationType.IDENTITY)
		private Long id;
		...
}
IDENTITY 사용 코드
Board board = new Board();
em.persist(board);
System.out.println("board.id = " + board.getId());

//출력: board.id = 1

<참고>

  • IDENTITY 전략과 최적화
    • 기본 키 값을 조회하기 위해선 INSERT 후 추가 SELECT가 필요 (2번통신)
    • JDBC3에 추가 된 Statement.getGeneratedKeys() 를 통해 저장과 동시에 키 값 조회(1번통신)

<주의>

  • 엔티티가 영속 상태가 되려면 식별자가 반드시 필요
  • IDENTITY 전략은 데이터베이스를 저장해야 식별자를 구할 수 있음 → em.persist()호출하는 즉시 INSERT SQL이 데이터베이스에 전달
  • 트랜잭션을 지원하는 쓰기 지연이 동작하지 않음

4.6.3 SEQUENCE 전략

  • 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트
  • 오라클, PostgreSQL, DB2, H@ 데이터베이스에서 사용 가능
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;
}
  • @GeneratedValue.generator를 통해 생성된 시퀀스를 할당 ( BOARD_SEQ_GENERATOR )
SEQUENCE 사용 코드
Board board = new Board();
em.persist(board);
System.out.println("board.id = " + board.getId());

//출력: board.id = 1

SEQUENCE - IDENTITY 실행 순서

  • SEQUENCE 전략 (식별자 조회 → 저장)
    1. em.persist()를 호출
    1. 데이터베이스 시퀀스를 사용해서 식별자 조회
    1. 조회한 식별자를 엔티티에 할당 후 영속성 컨텍스트에 저장
  • IDENTITY 전략 (저장 → 식별자 조회)
    1. em.persist()를 호출
    1. 엔티티를 데이터베이스에 저장
    1. 식별자를 조회해서 엔티티 식별자에 할당

@SequenceGenerator 속성

속성기능기본값
name식별자 생성기 이름 (id 역할)필수
sequenceName데이터베이스에 등록되어 있는 시퀀스 이름hibernate_sequence
initialValue시퀀스 DDL을 생성할때 처음 시작하는 수1
allocationSize시퀀스 한 번 호출에 증가하는 수50
catalog, schema 데이터베이스 catalog, schema 이름
제목 없음
매핑 시퀀스 생성 DDL
create sequence [sequenceName]
start with [initialValue] increment by [allocationSize]

<주의>

  • allocationSize default값이 50
  • 기본 값이 50인 이유는 최적화 때문, 시퀀스 증가값이 1이면 allocationsSize값을 1로 변경

<참고>

  • SEQUENCE 전략과 최적화
    • 데이터베이스 시퀀스를 통해 식별자 조회하는 추가 작업이 필요
      1. SELECT BOARD_SEQ.NEXTVAL FROM DUAL
      1. INSERT INTO BOARD...
    • JPA는 시퀀스 접근 횟수를 줄이기 위해 한번에 시퀀스 값을 증가시킨 후 메모리에 할당
      • 예) 50증가 후 메모리 1~50까지 식별자 할당, 51이 되면 100으로 증가 후 메모리 할당
    • 이런 상황이 부담스럽고 INSERT 성능이 중요하지 않으면 allocationSize의 값을 1로 설정
  • @SequenceGenerator는 @GeneratedValud 옆에 사용 가능
@Entity
public class Board {
		@ID
		@GeneratedValue(...)
		@SequenceGenerator(...)
		private Long id;
	
		...
}

4.6.4 TABLE 전략

  • 키 생성 전용 테이블을 하나 만들고 이름과 값으로 사용할 컬럼을 생성 (시퀀스를 흉내)
  • 테이블을 사용하므로 모든 데이터베이스 적용 가능
create table MY_SEQUENCES (
		sequence_name varchar(255) not null,
		next_val bigint,
		primary key ( sequence_name )
)
@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 전략 사용 코드
Board board = new Board();
em.persist(board);
System.out.println("board.id = " + board.getId());

//출력: board.id = 1

MY_SEQUENCES 결과 테이블

sequence_namenext_val
BOARD_SEQ2
MEMBER_SEQ10
PRODUCT_SEQ50

@TableGenerator 속성

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

매핑 시퀀스 생성 DDL, 테이블명 {table}

{pkColumnName}{valueColumnName}
{pkColumnValue}{initialValue}

<참고>

  • TABLE 전략과 최적화
    • 값을 조회하면서 SELECT 쿼리를 사용 다음 값으로 증가 시키기 위해 UPDATE쿼리 사용
    • SEQUENCE 전략과 비교해서 한번 더 통신하는 단점
    • @TableGenerator.allocationSize를 사용하여 최적화 (SEQUENCE의 최적화와 동일)

4.6.5 AUTO 전략

  • 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나 자동 선택 → 오라클: SEQUENCE, MySQL: IDENTITY 사용
  • @GeneratedValue.strategy의 기본값은 Auto
@Entity
public class Board {
		@Id
		@GeneratedValue //default GenerationType.AUTO
		//@GeneratedValue(strategy = GenerationType.AUTO)
		private Long id;
}

4.6.6 기본 키 매핑 정리

  • em.persist()를 호출한 직후 식별자 할당 전략별 발생하는 일
    • 직접 할당: 호출 전 직접 식별자 값을 할당, 없으면 예외 발생
    • SEQUENCE: DB 시퀀스에서 식별자 값 획득 후 영속성 컨텍스트 저장
    • TABLE: DB시퀀스 생성용 테이블 식별자 값 획득 후 영속성 컨텍스트 저장
    • IDENTITY: DB에 엔티티를 저장 후 식별자 값을 획득하여 영속성 컨텍스트에 저장

<참고>

  1. 권장하는 식별자 선택 전략
    • 데이터베이스 기본 키는 3가지 조건을 모두 만족
      1. null값은 허용하지 않음
      1. 유일 값
      1. 불변 값
    • 테이블의 기본 키를 선택하는 전략 크게 2가지
      • 자연 키(natural key)
        • 비즈니스에 의미가 있는 키
        • 예: 주민등록번호, 이메일, 전화번호
      • 대리 키(surrogate key)
        • 비즈니스와 관련없는 임의로 만들어진 키, 대체키 라고도 불림
        • 예: 오라클 시퀀스, auto_increment, 키생성 테이블
  1. 자연키 보다는 대리키를 권장
    • 현실과 비즈니스 규칙은 쉽게 변할 수 있으므로 대리키를 권장
  1. 비즈니스 환경은 언젠가 변화
    • 현재와 미래까지 충족하는 자연 키를 찾기가 어려움
    • 대리 키를 기본 키로 사용하고 자연 키 후보(주민등록번호)를 유니크 인덱스 설정을 권장
  1. JPA는 모든 엔티티에 일괄된 방식으로 대리 키 사용을 권장