김영한님이 지음, [자바 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번 통신한다.
식벽자를 구하기 위해 데이터베이스 시퀀스를 조회
SELECT BOARD_SEQ.NETVAL FROM DUAL
조회한 시퀀스를 기본 키 값으로 사용해 데이터베이스에 저장
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
: 데이터베이스에 엔티티를 저장해서 식별자 값을 획득한 후 영속성 컨텍스트에 저장
댓글남기기