BE/개발일지

데이터베이스 기본키 생성 전략

유쓰응 2024. 5. 15. 18:57

개발을 하다보면 DB연결을 통해 자바 객체를 데이터베이스에 저장하곤 한다.

이 과정에서 PK를 개발자가 직접 지정하지 않는다.

보통 아래와 같은 방식으로 pk외의 다른 컬럼에 대한 값을 지정하고 save를 하는 시점에서 자동으로 PK가 결정되게 한다.

@Builder
public Property(String zipCode, String roadNameAddress, String landLotNameAddress) {
    this.zipCode = zipCode;
    this.roadNameAddress = roadNameAddress;
    this.landLotNameAddress = landLotNameAddress;
}

기본 키는 기본 키 제약조건에 의해 아래와 같은 조건을 만족해야한다.

기본 키 제약조건

  1. null이 들어가면 안된다.
  2. 유일한 (unique) 한 값이어야함
  3. 변하는 값이면 안된다. (불변)
    이러한 이유 때문에 아래와 같이 Entity 클래스 자체에 builder를 쓰는 것 또는 Entity에 setter를 사용하는 것을 지양한다.
    • Entity에 setter를 쓰는 경우 → id를 변화시킬 수 있음
    • Entity 자체에 builder를 쓰는 경우 → id 값을 개발자가 설정할 수 있음.

따라서, 주민등록번호 또는 이메일과 같은 비즈니스 로직과 관련된 값을 PK로 사용하면 안된다.

어떠한 이유에서든 나중에 변할 수 있으니, 이러한 값들과 별개로 별도의 대체키를 사용해야한다.

이러한 대체키를 생성하는 전략은 3가지가 있는데 이에 관한 글을 정리해볼까 한다.

데이터베이스의 PK 생성 전략

  1. IDENTITY전략
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "property_id")
    private Long id;
    
    create table property
    (
        property_id                    bigint auto_increment -- 별도의 생성 전략이 명시됨
            primary key,
        property_land_lot_name_address varchar(255) null,
        property_road_name_address     varchar(255) null,
        property_zipcode               varchar(255) null
    );
    
    이 전략을 사용하면 서버에선 생성한 객체의 PK 값을 null로 비워두고 전송한다.
    JPA와 함께 사용하는 경우따라서 기본적으론 영속성 컨텍스트 (1차캐시)에 값을 보존하다 트랜잭션이 종료되는 시점에 필요한 쿼리를 DB에 전달한다.즉, 트랜잭션이 커밋되는 시점이 아닌, 영속성 컨텍스트에 들어가는 순간에 insert 쿼리가 실행된다.
    1. 하지만, IDENTITY 전략을 사용하는 경우엔 트랜잭션이 종료되지 않더라도 save() ( em.persist() )메서드가 실행되는 시점에 DB에 insert 쿼리를 전달한다.
    2. JPA는 Hibernate의 Transactional Write Behind(트랜잭션이 커밋될때까지 쿼리를 모아뒀다가 한번에 실행) 전략을 지원한다.
    3. 데이터베이스가 해당 컬럼을 생성할 때 값을 하나씩 증가시키며 PK를 배정한다.
    4. 아래와 같은 자바 코드로 PK를 생성하면 Table 생성 시 PK 전략을 auto_increment 로 설정한다.
  2. Sequence 전략 : 적당히 순서를 보존
    서버는 해당 값 내에서 생성할 객체에 PK를 배정하고 배정된 데이터를 DB에 insert 한다.
    DB에서 별도의 Sequence 값을 보존하고 있다가 allocationSize 만큼 값을 서버 메모리로 받아온다.
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE
            ,generator = "PROPERTY_SEQ_GENERATOR"
    )
    @Column(name = "property_id")
    private Long id;
    

    과정
    1. call next value for ~~ SEQ 를 통해 allocationSize만큼 서버로 할당 가능한 PK의 값을 전달한다.
    2. sequence가 8이고 allocationSize가 50이면 8 ~ 57까지 PK를 할당한다.
    3. 할당한 값을 배정하다가 57을 넘게 되면 다시 call next value ~ 를 통해 메모리로 값을 불러온다.
    4. (반복)
    단점 : 메모리에 값을 불러오고 저장하는데 일시적으로 서버가 중단되면 메모리에 있는 값이 날아간다.
    (추가로 MySQL에선 이 전략을 지원하지 않는다.)
  3. @SequenceGenerator( name = "PROPERTY_SEQ_GENERATOR", sequenceName = "PROPERTY_SEQ", initialValue = 1, allocationSize = 50 // 한번에 가져오는 할당할 PK를 )
  4. Table 전략
    1. 키 생성 전용 테이블을 만들어서 Sequence 전략과 유사하게 사용한다.
    2. 운영 환경에선 Table을 따로 관리해야하기 때문에 잘 사용하지 않는다고 한다.
    @TableGenerator(
            name = "PROPERTY_SEQ_GENERATOR",
            table = "SEQUENCE_TABLE",
            pkColumnName = "sequence_name" ,
            valueColumnName = "PROPERTY_SEQ",
            initialValue = 1,
            allocationSize = 200000
    )
    
  5. AUTO
    MySQL에서 어떤 전략을 사용하면 거기에 맞춰서, 다른 DB에서 다른 전략을 사용하면 그 전략에 맞춰서 사용하는 방식이다.전략은 위에서 언급한 IDENTITY, SEQUENCE, TABLE 셋 중 하나이다.
    1. @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "property_id") private Long id;
    2. 특정한 전략을 직접 설정하는 것이 아닌, 실제 사용하는 데이터베이스의 방언에 맞춰서 기본 전략을 채택하는 방법이다.