JPA의 GeneratedValue의 AUTO 전략, 이건 뭐죠?

봄도둑·2023년 6월 9일
0

Spring 개인 노트

목록 보기
12/17

엔티티의 식별자 생성 전략은 AUTO는 JPA에서 제공하는 기본적인 생성 전략입니다.

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

AUTO를 제외하고 IDENTITY, SEQUENCE, TABLE 3가지의 전략을 선택할 수 있습니다.

MySQL 환경과 jpa를 같이 쓰다보면 DB에 생성하지 않았던 테이블이 하나 불쑥 튀어 나옵니다.

hibernate_sequence는 무슨 녀석이길래 의도치 않은 테이블이 생성되어 무슨 동작을 하는 것일까요?

JPA를 처음 공부했을 때 들었던 의문 중 하나인, AUTO는 어떻게 식별자 키값을 생성하는 것일까에 대한 궁금증을 풀어봅니다.

1. AUTO 전략을 알아보기에 앞서

AUTO 전략에 앞서 JPA가 식별자를 생성하는 기본 전략들에 대해 간략히 설명하고 본론으로 들어가겠습니다.

첫번째, IDENTITY 전략입니다. IDENTITY 전략은 식별자 생성을 JPA가 처리 하는 것이 아니라 완전히 DB에 위임하는 것입니다. DB의 스키마 생성 시 지정했던 PK 생성 방식을 따라 만들게 됩니다. IDENTITY 전략을 사용하는 경우는 사용자가 DB 레벨에서 이미 PK 생성 규칙을 정했을 경우에 주로 사용합니다.

그렇다면 IDENTITY 전략 사용 시 엔티티 객체의 흐름을 살펴봅시다.

JPA에서 영속성 컨텍스트에서 엔티티 객체를 관리할 때 식별자 값을 반드시 들고 있어야 합니다. 그러나 식별자 생성 규칙은 DB에서 관리하기 때문에 직접 insert 하기 전까지 JPA가 동작하는 자바 코드 레벨에서는 식별자가 무엇인지 알 수 없습니다. 이러한 이유로 flush가 되는 시점이 일반적인 엔티티를 저장하는 시점과 차이가 나게 되는데, 일반적으로 트랜젝션이 종료되는 COMMIT이 일어나기 직전에 flush가 되지만, IDENTITY 전략의 경우 save() 메소드를 호출하고 있는 트랜잭션 중간에 insert 쿼리를 실행하고 flush가 됩니다.

두번째, SEQUENCE 전략입니다. SEQUENCE 전략은 DB의 시퀀스 객체를 조회해 유니크한 값을 생성합니다. 이 때 시퀀스 객체를 가지고 있는 DB의 종류는 정해져 있기 때문에 특정 DB에서만 해당 전략을 사용할 수 있습니다. 대표적으로 Oracle과 테스트용, 학습용으로 많이 사용하는 H2 DB가 있습니다.

IDENTITY 전략과는 다르게 일반적인 flush 시점을 가지고 있습니다. 자바 레벨에서 insert를 하기 전에 식별자 값을 명확하게 알 수 있기 때문에 트랜젝션 종료 직전 flush가 이뤄집니다. 좀 더 자세히 설명하자면 먼저 시퀀스 객체에서 select를 해서 엔티티에 넣을 식별자 값을 확보하기 때문에 insert 구문이 나가지 않아도 식별자 값을 알 수 있기 때문에 COMMIT을 하는 시점에 insert 쿼리를 실행하고 flush를 하게 됩니다.

세번째, TABLE 전략입니다. 이 전략은 DB의 시퀀스 객체를 대신할 테이블을 사용하기 때문에 sequence를 사용할 수 없는 DB들도 사용할 수 있는 전략입니다. 다만, 이 전략의 경우 DDL 자동 생성 설정을 사용하지 않았다면 시퀀스 객체를 대신할 테이블이 먼저 생성되어 있어야 합니다. DDL 자동 생성 설정을 사용하면 JPA가 직접 시퀀스 테이블을 생성합니다. @TableGenerator 어노테이션을 사용하면 시퀀스 테이블의 정보를 사용자 정의에 맞게 설정되어 생성할 수 있습니다.

JPA의 엔티티 객체의 흐름은 일반적인 JPA와 동일, 즉 SEQUENCE 전략과 동일한 흐름을 가지고 있습니다.

그렇다면 AUTO는 식별자를 어떻게 생성하는 것일까요?


(GenerationType의 AUTO에 대한 설명)

위의 사진은 AUTO 전략에 대한 설명을 담고 있습니다. 이를 조금 더 간략하게 표현하자면 식별자 생성 전략을 JPA에게 위임한다고 볼 수 있습니다.

JPA에게 위임한다는 건 전략 자체를 JPA가 결정한다는 것인데, 이 전략은 JPA 사용을 쉽게 만들어주는 요인 중 하나이기도 합니다. SEQUENCE 전략, IDENTITY 전략 처럼 DB에 종속적으로 코드를 개발할 필요도 없애주는 아주 편리한 전략입니다.

그렇다면, AUTO를 통해 JPA가 점지해주는 전략은 무슨 전략인 것일까요?


2. JPA의 AUTO는 어떻게 전략을 결정할까?

JPA의 AUTO 전략은 아래와 같은 로직에 의해 어떤 식별자 전략을 사용할 것인가를 결정합니다.

(이미지 출처 : 유영모님의 하이버네이트는 어떻게 자동 키 생성 전략을 결정하는가)

로직의 흐름을 자세히 살펴봅시다.

  1. 식별자의 데이터 타입이 UUID면 UUID Generator를 사용해 식별자 값을 생성한다.
  2. 식별자의 데이터 타입이 Numeral 계열이면 설정값 hibernate.id.new_generator_mappings 설정값을 확인한다.
    1. 설정값이 FALSE 일 경우 native Generator(IDENTITY) 전략을 사용해 식별자 값을 생성한다.
  3. 설정값이 TRUE일 경우 DB가 시퀀스 객체를 지원하는지 확인한다.
    1. 시퀀스 객체를 지원할 경우 SEQUENCE Generator를 사용해 식별자 값을 생성한다.
    2. 시퀀스 객체를 지원하지 않을 경우 TABLE Generator를 사용해 식별자 값을 생성한다.

hibernate.id.new_generator_mappings의 경우 하이버네이트 설정을 관리하는 persistence.xml 파일에서 별도로 값을 지정할 수 있고, 지정하지 않았을 경우 기본값으로 TRUE 설정됩니다.

그렇기 때문에 MySQL에서 AUTO로 식별자 생성 전략을 설정할 경우 TABLE Generator를 통해 식별자가 생성됩니다. 위에서 살펴 보았던 hibernate_sequence 테이블은 TABLE 전략에 따른 시퀀스 객체를 대신할 테이블이었던 것입니다.

hibernate_sequence의 존재를 알았으니 이제 TABLE 전략이 과연 최적의 전략인가에 대한 부분입니다.


3. TABLE 전략과 MySQL

TABLE 전략의 큰 강점 중 하나는 바로 DB에 종속적이지 않다는 것입니다. 시퀀스 객체를 테이블로 대신 처리 하기 때문에 DB가 무엇이든 상관이 없게 됩니다.

하지만 매번 DB에 접근해서 값을 select 한 후, update해서 테이블의 값을 증가 시키고, 받아온 식별자 값을 다시 insert하는 것은 성능에 문제가 있어 보입니다. 그리고 많은 참조글에서 TABLE 전략은 성능이 좋지 않기 때문에 사용하는 것을 권장하지 않는다고 합니다.

그렇다면 TABLE 전략은 사용해서는 안되는 불필요한 전략일까요?

제 생각은 다릅니다. TABLE 전략은 기본적으로 모든 데이터베이스 벤더에 사용할 수 있도록 만들어진 전략입니다. 모든 데이터베이스에서 사용할 수 있다는 것은 백엔드가 DB에 의존하지 않게 된다는 것입니다.

SEQUENCE 전략과 IDENTITY 전략은 백엔드가 DB의 종류에 종속되도록 만듭니다. 시퀀스 객체가 없는 DB일 경우 IDENTITY 전략을, 식별자 생성을 DB에 위임할 수 없는 경우 SEQUENCE 전략을 취하게 됩니다. 즉, 백엔드의 DB 의존도가 올라가게 됩니다.

제가 이전에 작성 했던 글에서 다뤘듯이 계층 간의 의존이 발생할 경우 변경에 유연하게 대응하기 어려운 설계상의 단점을 가지게 됩니다.

그렇기 때문에 TABLE 전략을 무작정 IDENTITY 전략으로 변경하는 것은 좋은 개발 방향이 아니라고 생각합니다.

IDENTITY 전략의 경우 앞서 살펴 보았듯이 먼저 insert 쿼리 실행해 식별자를 받아옵니다. 즉, JPA에서 제공하는 쓰기 지연을 통해 데이터를 넣을 수 없게 됩니다. 이로 인해 batch size를 적용해 한 번에 데이터를 밀어 넣는 방법이 동작하지 않습니다. (하이버네이트 공식 문서 참조)

즉, MySQL을 사용하면서 대량의 데이터를 일괄적으로 저장이 필요할 경우 TABLE 전략을 사용하되, 성능의 이슈가 있을 경우 allocationsize를 통해 성능 최적화를 진행하는 과정을 거쳐야 합니다.

식별자 생성 전략은 각각 trade off가 존재합니다. 개발하는 프로젝트의 상황, 혹은 추구하는 아키텍처의 가치 등을 고려해 최적의 전략이라고 판단하는 것을 선택해야 합니다.


*REFFERNCE

profile
Java Spring 백엔드 개발자입니다. java 외에도 다양하고 흥미로운 언어와 프레임워크를 학습하는 것을 좋아합니다.

0개의 댓글