[JPA] Primary Key 매핑 (GeneratedValue strategy)

3Beom's 개발 블로그·2022년 10월 10일
0

SpringJPA

목록 보기
4/21
post-custom-banner

출처

본 글은 인프런의 김영한님 강의 자바 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 값을 바로 바로 알아채기 위해 다음과 같은 과정으로 동작한다.
  1. JPA 상에서 DB에 저장할 데이터를 담은 새로운 객체를 생성한다.
    (PK는 비어있다.)
  2. JPA 상에서 persist()가 호출된다.
  3. DB에 바로 INSERT 쿼리를 날린다.
  4. DB에 데이터가 저장되며 생성되는 PK 값을 받아온다.
  5. 받아온 PK 값을 객체에 넣는다. (이제 PK가 채워졌다.)
  6. 해당 객체 데이터를 영속성 컨텍스트에 저장한다.
  • 즉, 원래는 persist()에 해당하는 쿼리를 영속성 컨텍스트 내에 쌓아두었다가 commit()을 만나면(flush되면) 한번에 전송되어야 하지만, IDENTITY 특성 상 그럴 수 없어 바로 바로 쿼리를 날리는 것이다.
  • 이러한 동작 과정으로 인해 1차 캐시를 활용한 버퍼링 기능을 구현해야 할 경우, 제한될 수 있다.

3. GenerationType.SEQUENCE

@Entity
@SequenceGenerator(
	name = "COOKIE_SEQ_GENERATOR", // Generator 이름 (JPA에서 활용)
    sequenceName = "COOKIE_SEQ", // Sequence 이름 (DB 내 Sequence의 이름)
    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 오브젝트로부터 해당 값을 얻어올 수 있기 때문이다.
  • 이에 따라 다음과 같이 동작하게 된다.
  1. JPA 상에서 DB에 저장할 데이터를 담은 새로운 객체를 생성한다.
    (PK는 비어있다.)
  2. JPA 상에서 persist()가 호출된다.
  3. DB에게 해당 SEQUENCE의 다음 PK 값을 물어보는 쿼리를 보낸다.
  4. 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씩 증가하게 된다.
  • 다음과 같이 동작한다.
  1. 처음 persist()가 호출되면, DB로부터 Sequence 값 1을 받는다.
  2. 바로 한번 더 소통해서 DB의 Sequence 값을 51까지 올린다.
  3. 이후에 PK 값 50까지는 persist()가 호출되어도 DB와 소통하지 않고 Memory에서 꺼내다 쓴다.
  4. 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를 활용한 최적화가 가능하다.)
profile
경험과 기록으로 성장하기
post-custom-banner

0개의 댓글