본문 바로가기

개발/Spring boot

[Spring boot] JPA 기본 키 생성 전략(AUTO, IDENTITY, SEQUENCE, TABLE, UUID)

Index에 대해 공부하다가 JPA의 기본 키 생성 전략들은 어떤 방법으로 생성하는 편리한 어노테이션들인지에 대해 공부하게 되었다.

JPA 기본 키 생성 전략

JPA(Java Persistence API)에서는 엔티티의 기본 키를 생성하기 위해 다양한 전략을 제공한다.

다음은 5가지의 엔티티 기본키 생성 전략이다.

AUTO, IDENTITY, SEQUENCE, TABLE, UUID

 

1. @GeneratedValue(strategy=GenerationType.AUTO)

  • 자동으로 기본 키를 생성하며, 데이터베이스 벤더(DBMS를 개발하고 판매하는 회사)마다 다른 전략을 사용한다. 따라서 개발자가 직접 전략을 선택할 필요가 없다.
  • 다음은 데이터베이스 벤더에 따라 사용하는 전략이다.
    1. MySQL: AUTO_INCREMENT
    2. PostgreSQL: SERIAL
    3. Oracle: 시퀀스를 사용하여 자동으로 증가하는 값 생성
    4. SQL Server: IDENTITY
  • 따라서, 데이터베이스를 변경하게 되더라도 @GeneratedValue(strategy=GenerationType.AUTO)를 사용하면 코드 수정 없이 자동으로 기본 키 생성 전략이 변경된다.
  • 이 전략은 트랜잭션이 커밋될 때마다 데이터베이스에 새로운 값을 요청하기 때문에 성능 문제를 일으킬 수 있다. 따라서, 대규모 데이터를 다룰 때는 다른 전략을 사용하는 것이 좋음.

2. @GeneratedValue(strategy=GenerationType.IDENTITY)

  • 데이터베이스에서 기본 키 생성을 담당하는 AUTO_INCREMENT, SERIAL, IDENTITY 등의 컬럼을 사용하며, 개발자가 직접 기본 키를 지정하지 않아도 데이터베이스에서 자동으로 값이 증가한다.
  • 기본 키가 NULL인 경우에만 자동으로 값이 증가한다.
  • IDENTITY 전략은 id 값을 설정하지 않고(null) INSERT Query를 날리면 그때 id의 값을 세팅한다. AUTO_INCREMENT는 DB에 INSERT SQL을 실행한 이후에 id 값을 알 수 있다.
  • 영속성 컨텍스트에서 해당 객체가 관리되려면 무조건 pk 값이 있어야 한다. 하지만 이 경우 pk 값은 DB에 들어가봐야 알 수가 있다. 다시 말해서, IDENTITY 전략의 경우 영속성 컨텍스트의 1차 캐시 안에 있는 @Id 값은 DB에 넣기 전까지는 세팅을 할 수 없다는 것이다. (JPA 입장에서는 Map의 key 값이 없으니까 해당 객체의 값을 넣을 수 있는 방법이 없다.)
  • entityManager.persist() 메소드 호출을 통해 영속성 컨텍스트에 등록하게 될 경우 에 즉시 INSERT SQL을 실행하고 DB에서 식별자를 조회한다. INSERT 쿼리 실행 후에도 자동으로 생성된 기본 키 값을 확인하려면 추가적인 SELECT 쿼리를 실행해야 하기 때문에, 대량의 데이터를 다룰 때는 성능 문제가 발생할 수 있다.

왜 성능 문제가 발생하는가?

  • IDENTITY 전략을 사용하면, 엔티티를 저장할 때마다 데이터베이스에서 해당 테이블의 AUTO_INCREMENT 컬럼 값을 찾아서 업데이트하게 됩니다. 이 과정에서 데이터베이스와의 I/O 작업이 발생하므로, 대량의 데이터를 다룰 때 성능 문제가 발생할 수 있습니다.

대량의 데이터를 한 번에 저장하는 경우

  • 데이터베이스에서 AUTO_INCREMENT 값을 찾아서 업데이트해야 하므로 성능이 저하 될 수 있음
  • IDENTITY 전략을 사용하면 엔티티를 저장하기 전에는 ID 값을 알 수 없으므로, 트랜잭션 내에서 여러 번 엔티티를 저장하는 경우에도 매번 데이터베이스에서 AUTO_INCREMENT 값을 찾아서 업데이트해야 하기 때문에 저하 될 수 있음.

개선 방안

  • 대량의 데이터를 다룰 때에는 IDENTITY 전략 대신에 SEQUENCE 전략을 사용해야 함.
  • SEQUENCE 전략은 미리 데이터베이스에서 시퀀스 값을 가져와서 메모리에서 관리하므로, 데이터베이스와의 I/O 작업을 줄일 수 있음. 또한, 시퀀스 값을 미리 가져오므로, 여러 번 엔티티를 저장하는 경우에도 매번 시퀀스 값을 가져오지 않아도 되므로 성능이 개선됨

영속화된 엔티티는 데이터베이스와 연결되어 있으므로, 영속성 컨텍스트에서 변경된 내용이 있다면, 트랜잭션이 커밋되는 시점에 해당 변경 내용이 데이터베이스에 반영.

 

3. @GeneratedValue(strategy=GenerationType.SEQUENCE)

  • 시퀀스를 이용하여 기본 키를 생성하는 전략이며, Oracle, PostgreSQL 등에서 사용이 가능하다.
  • @GeneratedValue(strategy=GenerationType.SEQUENCE)를 사용할 경우, 시퀀스를 생성한 후에 다음 시퀀스 값을 기본 키로 사용합니다. 이 경우, 시퀀스 값은 JPA에서 관리하지 않으며, 데이터베이스에서 관리합니다. 이 때문에, 여러 클라이언트에서 같은 시퀀스 값을 사용하게 되는 경우 충돌이 발생할 수 있음.
  • 따라서, @SequenceGenerator 어노테이션을 함께 사용하여 시퀀스 생성기를 지정할 수 있음.
  • @GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "seq") 와 같이 @SequenceGenerator 어노테이션을 함께 사용할 수 있음.
  • 주의할 점은 시퀀스 값은 트랜잭션 시작 시점에서 이미 결정되므로, 영속성 컨텍스트에서 관리되지 않고 즉시 데이터베이스에 저장됩니다. 따라서, 시퀀스 값을 미리 가져와 사용할 수 없으며, 기본 키 값이 필요한 경우 트랜잭션이 커밋되기 전까지는 알 수 없다.

4. @GeneratedValue(strategy=GenerationType.TABLE)

  • 데이터베이스의 키 생성 전용 테이블을 이용하여 기본 키를 생성하는 전략
  • 데이터베이스에 키 생성용 테이블을 만들어서 사용해야 한다. 이 테이블은 기본 키 값의 범위를 저장하고 관리하는데 사용한다.
  • 이 전략은 데이터베이스의 성능에 영향을 미칠 수 있으며, 대량의 데이터를 다룰 때는 성능 문제가 발생할 수 있으며, 데이터베이스에서 키 생성용 테이블을 관리하므로, 트랜잭션 충돌이 발생할 수 있음.
  • @GeneratedValue(strategy=GenerationType.TABLE, generator = "tbl") 와 같이 @TableGenerator 어노테이션을 함께 사용할 수 있음.
  • 이 방법은 다른 전략과는 달리 데이터베이스 벤더나 버전에 따라 호환성이 높기 때문에 이식성이 높은 전략임

트랜잭션 충돌 상황

  1. 두 개의 트랜잭션이 동시에 실행되고 있음.
  2. 각각의 트랜잭션에서는 키 생성 테이블에서 다음 키 값을 가져와서 다음 키 값을 계산한 뒤
  3. 테이블에 저장하는 작업을 수행함.
  4. 이 때, 첫 번째 트랜잭션에서 키 생성 테이블에서 다음 키 값을 가져왔지만, 아직 테이블에 저장하지 않은 상태이고, 두 번째 트랜잭션에서도 같은 값을 가져온 후에 테이블에 저장하려고 시도함.
  5. 이 경우, 두 번째 트랜잭션에서는 키 값이 이미 사용 중이라는 예외가 발생

5. @GeneratedValue(strategy=GenerationType.UUID)

  • UUID(Universally Unique Identifier)를 사용하여 기본 키를 생성하는 전략.
  • 전통적인 숫자 기반의 기본 키 생성 방법과 달리 문자열 기반으로 생성하기 때문에, 데이터베이스에서 별도의 시퀀스나 테이블을 생성할 필요없음
  • UUID는 128비트의 숫자로 구성되며, 유일성이 보장된다. UUID를 생성하는 방법은 랜덤 값을 사용하는 방법과, 시간 정보와 랜덤 값을 조합하여 생성하는 방법이 있음
  • UUID는 문자열로 구성되므로, 기본 키 값이 숫자일 때와 달리 인덱스를 사용할 때 성능에 영향을 미칠 수 있다. 또한, UUID는 고유성을 보장하지만, 순서를 보장하지 않기 때문에 순서에 의존하는 비지니스 로직에서는 추천하지 않음.