본문 바로가기

스프링 부트/JPA

[JPA] 기본 키 생성 전략과 각 전략의 차이 - GenerationType

JPA에서 Entity 객체를 정의할 때  @Id 속성을 함께 정의하여야 한다. 
이 Id를 정의할 때에는 직접 할당하는 방법자동 생성하는 방법이 있다.

직접 할당하는 방법 

  • @Id 어노테이션만으로 id를 지정한다. 

자동 생성

  • @Id와 @GeneratedValue를 같이 사용한다.
  • GenerationType 옵션으로 전략을 지정한다.

GenerationType 종류

TABLE

특정 벤더에 의존적이지 않다.
시퀀스 테이블을 만들어서 데이터베이스 시퀀스를 흉내낼 Id를 할당한다. 
이 전략을 사용할 시, jpa ddl auto 설정이 되어 있지 않았다면 해당 시퀀스 테이블 생성이 선행이 되어야한다. 
@TableGenerator(테이블 생성 어노테이션)과 함께 사용할 수 있다.
만약 jpa ddl auto 설정이 되어있다면 hibernate가 시퀀스 테이블을 생성한다.

@GeneratedValue(strategy=GenerationType.TABLE, generator = "MEMBER_SEQ_GENERATOR")
@TableGenerator(
name="MEMBER_SEQ_GENERATOR",
table="MY_SEQUENCE", //시퀀스 생성용 테이블 이름
pkColumnName="sequence_name", //MY_SEQUENCE 테이블에 생성할 필드이름(시퀀스네임)
pkColumnValue="MEMBER_SEQ", //SEQ_NAME이라고 지은 칼럼명에 들어가는 값.(키로 사용할 값)
allocationSize=50
)
private Long id;

// ...

 

IDENTITY

기본키 생성을 DB에게 위임한다. (MySQL의 경우는 AUTO INCREMENT)
즉 특정 DB 벤더에 의존적이다.
Id가 null일 경우 해당 객체의 Id를 DB의 AUTO_INCREMENT를 가져와 할당한다. 

@Entity
public class Feed extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    // ...
}

 

➕ 객체를 저장할 때 벌어지는 일

JPA에서 영속성 컨텍스트에서 객체를 관리하기 위해서는 무조건 ID 속성, 즉 PK 값이 있어야 한다.
(JPA 입장에서는 Map의 key 값이 없다면 해당 객체의 값을 넣을 수 있는 방법이 없기 때문이다.)
그런데 AUTO_INCREMENT 경우 애플리케이션에서는 그 값이 얼마임을 알지 못하기에 DB에 insert문을 날려야 id 값을 알 수 있다.

이말인 즉슨
객체를 save() 메서드로 저장을 할 때 원래는 트랜잭션이 끝나는 COMMIT 시점 전에 flush가 이루어지지만,
해당 경우는 DB에서 id 값을 받아와야 하니 save() 메서드를 호출하는 시점에 insert 쿼리가 나간다. (flush가 이루어진다.)

이렇게 id 값을 할당한 객체는 영속성 컨텍스트 1차 캐시에 값을 넣게된다. 



SEQUENCE

DB의 sequence 객체를 이용해 유일한 값을 순서대로 생성한다. 
즉 특정 DB 벤더에 의존적이다. 
이 전략은 sequence를 사용하는 Oracle, DB2, H2 등의 DB에서 사용한다. 
@SequenceGenerator(시퀀스를 생성하는 어노테이션)과 함께 사용할 수 있다.
만약 sequence 객체를 사용하지 않는 DB 벤더를 이용할 경우 Sequence를 관리할 객체(테이블)을 생성한다. 
단 jpa ddl auto 설정이 되어있어야 한다.

@Entity
@SequenceGenerator(
        name="BOARD_SEQ_GENERATOR",
        sequenceName="BOARD_SEQ",
        initialValue=1,allocationsSize=1
)
  • name : 실제 @Id 필드에서 참조할 이름
  • sequenceName : 실제 데이터베이스에 생성되는 시퀀스 객체 이름
  • allocationsSize : DB에서 가져오는 시퀀스 호출에 증가하는 값의 크기

➕ 객체를 저장할 때 벌어지는 일

IDENTITY 전략과 다른점이 존재한다. 
해당 전략은 단순히 sequence 값만 시퀀스 객체에서 조회해오면 되기에, 엔티티 테이블에 해당 객체를 바로 insert 할 필요가 없다.

이말인 즉슨
객체를 save() 메서드로 저장을 할 때 id 값을 채우기 위해 select 문이 실행되고, 트랜잭션이 끝나는 COMMIT 시점에 insert 문이 실행된다. 

AUTO

DB 벤더에 따라 자동으로 3가지 전략 중 하나를 선택한다. 
DB 벤더를 변경해도 전략을 수정하지 않아도 되지만, 
SEQUENCE나 TABLE 전략을 선택한다면 sequence나 키 테이블을 생성해두어야 함을 주의하자.
근데 JPA ddl-auto 기능을 사용하면 위의 예제 처럼 hibernate가 알아서 시퀀스 테이블을 생성해준다.

 

IDENTITY, SEQUENCE, TABLE의 객체 저장 비교

먼저 다음과 같은 테스트용 Entity를 생성했다. 

@Entity
public class TestEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE) // or IDENTITY
    private Integer id;

    @Column(name = "name")
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}


다음과 같은 테스트를 구현하고 확인해본다. 

@DataJpaTest
class TestEntityRepositoryTest {

    @Autowired
    TestEntityRepository repository;

    @Test
    void test() {
        final int count = 3;

        for(int i = 0; i < count; i++) {
            TestEntity saved = new TestEntity();
            saved.setName("이름" + i);
            repository.save(saved);
            System.out.println("객체 저장, insert 쿼리가 나가고 있나여?");
        }

        List<TestEntity> res = repository.findAll();

        assertThat(count).isEqualTo(res.size());
    }
}

SEQUENCE 전략 로그

시퀀스 테이블에 대한 정의가 없었기에 hibernate가 알아서 시퀀스 객체를 생성하고 관리한다. 
id 값을 채우기 위해 sequence 값을 불러오고, 
해당 테스트가 끝나는 시점, 즉 트랜잭션이 끝나는 시점에 insert 쿼리가 flush됨을 확인할 수 있다.

IDENTITY 전략 로그

auto_increment id 값을 할당하기 위해 먼저 DB에 저장이 필요하다.
때문에 먼저 insert 쿼리가 save가 실행될 때마다 flush 됨을 확인할 수 있다. 

TABLE 전략 로그

sequence 테이블에서 id 값을 얻어오기 위해 select 문이 나가고,
next_val을 증가시키기 위해 update 쿼리가 나간다. 
객체 save 시점에서 insert 쿼리가 나가지 않고 트랜잭션이 끝날 시점에 한번에 insert 쿼리가 나가는 것을 볼 수 있다.

 

참고 자료

추가로 보면 좋을 것 같은 글