출처
본 글은 인프런의 김영한님 강의 자바 ORM 표준 JPA 프로그래밍 - 기본편
을 수강하며 기록한 필기 내용을 정리한 글입니다.
-> 인프런
-> 자바 ORM 표준 JPA 프로그래밍 - 기본편 강의
1. Primary Key 매핑
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
- 위 예시와 같이 Primary Key를 매핑할 때, 두 가지 Annotation이 활용된다.
-> @Id, @GeneratedValue
- @Id : 해당 필드가 Primary Key에 해당한다는 것을 의미한다. 해당 Annotation만 붙일 경우, DB에 저장할 때 PK 값을 직접 지정해 주어야 한다.
- @GeneratedValue : 자동으로 PK 값이 정해지도록 설정하는 Annotation이다. PK값이 정해지는 전략을 결정하는 'strategy' 속성이 있으며, 다음 4가지 타입 중 하나로 설정될 수 있다.
-> GenerationType.AUTO, GenerationType.IDENTITY, GenerationType.SEQUENCE, GenerationType.TABLE
- 보통 PK 값을 하나 하나 다 지정해 주기엔 무리가 있으므로, @GeneratedValue를 활용해서 자동으로 PK 값이 생성되도록 설정하며, 상기 4가지 전략 중 하나를 선택한다.
- GenerationType.AUTO는 현재 연결된 DB에 맞춰 본인을 제외한 3가지 중에서 자동으로 지정된다.
2. GenerationType.IDENTITY
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
- IDENTITY는 PK 값 생성 과정을 DB에게 맡기는 방식이다.
- MySQL에서 AUTO INCREMENT를 붙인 것과 같다고 생각하면 된다.
(실제로 MySQL을 활용하며 IDENTITY 전략을 쓰면, DB 테이블 생성할 때 AUTO INCREMENT가 붙는다.)
2-1. IDENTITY 전략의 특징(단점?)
- IDENTITY 전략을 활용할 경우, PK 값을 DB에서 생성하기 때문에 JPA 상에서는 사전에 PK 값을 알 수 없다.
-> DB에 데이터가 입력되기 전까지는 PK 값을 모른다..!
- 그냥 그대로 쓰게 되면, JPA 상에서 persist() 함수를 활용할 때 영속성 컨텍스트에 PK가 누락될 것이다.
-> 이후에 데이터 조회 과정이 이루어 질 경우, 영속성 컨텍스트에서 PK를 기준으로 조회하게 될 텐데, 그럴 수 없게 된다.
-> 영속성 컨텍스트?
- 이에 따라 IDENTITY 전략은 PK 값을 바로 바로 알아채기 위해 다음과 같은 과정으로 동작한다.
- JPA 상에서 DB에 저장할 데이터를 담은 새로운 객체를 생성한다.
(PK는 비어있다.)
- JPA 상에서 persist()가 호출된다.
- DB에 바로 INSERT 쿼리를 날린다.
- DB에 데이터가 저장되며 생성되는 PK 값을 받아온다.
- 받아온 PK 값을 객체에 넣는다. (이제 PK가 채워졌다.)
- 해당 객체 데이터를 영속성 컨텍스트에 저장한다.
- 즉, 원래는 persist()에 해당하는 쿼리를 영속성 컨텍스트 내에 쌓아두었다가 commit()을 만나면(flush되면) 한번에 전송되어야 하지만, IDENTITY 특성 상 그럴 수 없어 바로 바로 쿼리를 날리는 것이다.
- 이러한 동작 과정으로 인해 1차 캐시를 활용한 버퍼링 기능을 구현해야 할 경우, 제한될 수 있다.
3. GenerationType.SEQUENCE
@Entity
@SequenceGenerator(
name = "COOKIE_SEQ_GENERATOR",
sequenceName = "COOKIE_SEQ",
initialValue = 1, allocationSize = 1)
public class Cookie{
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "COOKIE_SEQ_GENERATOR")
private Long id;
~~
- DB의 Sequence 오브젝트를 활용한다. (ORACLE)
- 위 예시와 같이 @SequenceGenerator Annotation을 활용하여 Sequence를 직접 생성한 후, 엔티티와 매핑시킬 수 있다.
(Sequence를 직접 안만들면 Hibernate에서 자동으로 만들어주긴 하지만, 테이블마다 Sequence를 따로 두고 관리하는게 더 좋다고 한다.)
3-1. SEQUENCE 전략의 특징
- 앞서 기재한 IDENTITY 전략의 경우, persist()를 호출하면 바로 바로 DB에 해당 쿼리를 보냈지만, SEQUENCE는 그렇지 않다.
- SEQUENCE 전략도 DB에서 PK 값을 생성하지만, DB 내 SEQUENCE 오브젝트로부터 해당 값을 얻어올 수 있기 때문이다.
- 이에 따라 다음과 같이 동작하게 된다.
- JPA 상에서 DB에 저장할 데이터를 담은 새로운 객체를 생성한다.
(PK는 비어있다.)
- JPA 상에서 persist()가 호출된다.
- DB에게 해당 SEQUENCE의 다음 PK 값을 물어보는 쿼리를 보낸다.
- PK 값을 받아오면 이를 객체에 포함시켜 영속성 컨텍스트에 저장한다.
- 따라서 SEQUENCE 전략은 IDENTITY와 달리 원래 JPA 동작 방식 그대로 persist()를 모아서 한번에 보낼 수 있다.
(1차 캐시 활용한 버퍼링 가능..!!)
3-2. SEQUENCE 전략 최적화
- 위에서 언급한 SEQUENCE 동작 과정을 보면, persist() 할 때마다 DB와의 소통이 계속 이루어져야 하는 것을 확인할 수 있다.
- 이렇게 불필요한 소통이 많은 것을 'allocationSize' 속성으로 보완할 수 있다.
@Entity
@SequenceGenerator(name = "COOKIE_SEQ_GENERATOR", sequenceName = "COOKIE_SEQ",
initialValue = 1, allocationSize = 50)
public class Cookie{
@Id
@GenerateValue(strategy = GenerationType.SEQUENCE, generator = "COOKIE_SEQ_GENERATOR")
private Long id;
~~
- 위 예시를 살펴보면 'allocationSize' 값이 50으로 설정되어 있는 것을 확인할 수 있다.
- 이는 Memory 상에서 Sequence의 PK 값을 50개까지 저장해 둔다는 의미이며, DB의 Sequence 값은 50씩 증가하게 된다.
- 다음과 같이 동작한다.
- 처음 persist()가 호출되면, DB로부터 Sequence 값 1을 받는다.
- 바로 한번 더 소통해서 DB의 Sequence 값을 51까지 올린다.
- 이후에 PK 값 50까지는 persist()가 호출되어도 DB와 소통하지 않고 Memory에서 꺼내다 쓴다.
- Memory 상의 Sequence 값이 50에 다다르면, DB와 소통해서 DB의 Sequence 값을 101까지 올리고, Memory에서 51부터 100까지 또 제공하게 된다.
- 해당 과정을 통해 DB와의 불필요한 소통을 줄일 수 있게 된다.
- 여기서 allocationSize를 너무 크게 잡아버리면, 애플리케이션을 잠시 종료시킬 일이 있을 때 Memory가 다 날아가 버리기 때문에 그만큼 빈 숫자가 생겨버린다.
(allocationSize를 10000으로 잡아뒀다가 이런일이 벌어지면, PK값이 10 다음 10011로 설정되어 버릴 수 있다..!)
- 따라서 50~100 사이로 적당히 설정하는 것이 좋다.
4. GenerationType.TABLE
@Entity
@TableGenerator(name = "COOKIE_SEQ_GENERATOR", table = "SEQUENCES", pkColumnValue = "COOKIE_SEQ", allocationSize = 1)
public class Cookie{
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "COOKIE_SEQ_GENERATOR")
private Long id;
~~
- TABLE 전략은 PK를 생성하기 위한 테이블을 추가로 만드는 것이다.
- 위 예시와 같이 @TableGenerator Annotation으로 생성할 테이블에 대한 정보를 설정할 수 있다.
- 종류 상관없이 모든 DB에서 적용할 수 있다는 장점이 있지만, 아무래도 테이블을 하나 추가로 두는 방식이다 보니 성능 상에 이슈가 있다고 한다.
4-1. TABLE 전략 동작 방식
- 위 예시에서 @TableGenerator Annotation을 살펴보자.
@TableGenerator(name = "COOKIE_SEQ_GENERATOR",
table = "SEQUENCES",
pkColumnValue = "COOKIE_SEQ",
allocationSize = 1)
- name 속성은 JPA 상에서 쓰이는 이름이다.
- table 속성으로 전달된 이름으로 PK 전용 테이블이 만들어진다.
(DB에 'SEQUENCES' 라는 이름의 테이블이 추가된다.)
- 해당 테이블 내에 pkColumnValue 속성으로 전달된 이름의 Column이 추가된다.
('SEQUENCES' 테이블에 'COOKIE_SEQ' Column이 추가된다.)
- 해당 Column은 1부터 allocationSize 만큼 증가하며 PK를 제공해 준다.
(SEQUENCE와 동일한 방식으로 allocationSize를 활용한 최적화가 가능하다.)