[JPA] Chapter 4. 엔티티 매핑 2 - 기본 키(Primary Key)

joyful·2021년 7월 12일
0

JPA

목록 보기
5/18

들어가기 앞서

이 글은 김영한 님의 저서 「자바 ORM 표준 JPA 프로그래밍」을 학습한 내용을 정리한 글입니다. 모든 출처는 해당 저서에 있습니다.


4.4 기본 키 매핑

4.4.1 기본 키 직접 할당 전략

기본 키를 애플리케이션에서 직접 할당하는 방식

  • @Id로 매핑
    • @Id 적용 가능 자바 타입
      • 자바 기본형 ex) int, float
      • 자바 래퍼(Wrapper)형 ex) Integer, Float
      • String
      • java.util.Date
      • java.sql.Date
      • java.math.BigDecimal
      • java.math.BigInteger

  • em.persist()로 엔티티를 저장하기 전 기본 키 직접 할당
Board board = new Board();
board.setId("id1")  //기본 키 직접 할당
em.persist(board);

💡 식별자 값 없이 저장시 예외가 발생한다.



4.4.2 기본 키 자동 생성 전략

대리 키 사용하여 생성하는 방식

  • @Id@GeneratedValue 추가하여 원하는 키 생성 전략 선택

  • 데이터베이스 벤더마다 기본 키 생성 지원 방식이 다르므로 종류가 다양함

    💡 hibernate.id.new_generator_mappings

    • 키 생성 전략 사용시 persistence.xml에 필수로 추가
    • 기본값 : false(과거 버전과의 호환성 유지를 위함)
    • 기존 하이버네이트 시스템 유지보수를 제외하고는 true로 설정할 것

✅ IDENTITY 전략

  • 기본 키 생성을 데이터베이스에 위임하는 전략
    → 사용하는 데이터베이스에 의존
  • 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용
  • 데이터베이스에 값을 저장(INSERT)한 후 기본 키 값 조회 가능
    → 엔티티에 식별자 값 할당하기 위해 데이터베이스 추가 조회

💻 매핑 코드

@Entity
public class Board {

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

💻 사용 코드

private static void logic(EntityManager em) {
    Board board = new Board();
    em.persist(board);  //엔티티 저장
    System.out.println("board.id = " + board.getId());  //할당된 식별자 값 출력
}

💻 출력

board.id = 1  //저장 시점에 데이터베이스가 생성한 값을 JPA가 조회

💡 Statement.getGeneratedKeys()

  • JDBC3에 추가된 메소드
  • 데이터를 저장하면서 동시에 생성된 기본 키 값 조회 가능
  • 데이터베이스와 한 번만 통신 → 최적화

💡 주의

em.persist()를 호출하는 즉시 INSERT SQL이 데이터베이스에 전달
→ 트랜잭션을 지원하는 쓰기 지연 동작 X


✅ SEQUENCE 전략

  • 데이터베이스 시퀀스를 사용하여 기본키를 생성하는 전략
    → 사용하는 데이터베이스에 의존

    💡 데이터베이스 시퀀스

    유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트

  • 시퀀스를 지원하는 ORACLE, PostgreSQL, DB2, H2에서 사용

💻 DDL

CREATE TABLE BOARD (
    ID BIGINT NOT NULL PRIMARY KEY,
    DATA VARCHAR(255)
)

//시퀀스 생성
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;

💻 매핑 코드

@Entity
@SequenceGenerator(  //시퀀스 생성기 등록
    name = "BOARD_SEQ_GENERATOR",
    sequenceName = "BOARD_SEQ",  //매핑할 실제 데이터베이스 시퀀스 이름
    initialValue = 1, allocationSize = 1
)
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "BOARD_SEQ_GENERATOR"  //시퀀스 생성기 선택
    )						       //id 식별자 값 할당 담당
    private Long id;
    ...
}

💻 사용 코드

private static void logic(EntityManager em) {
    Board board = new Board();
    em.persist(board);
    System.out.println("board.id = " + board.getId());
}
  1. em.persist() 호출시 데이터베이스 시퀀스 사용하여 식별자 조회
  2. 조회한 식별자를 엔티티에 할당한 후 영속성 컨텍스트에 엔티티 저장
  3. 트랜잭션 커밋하여 플러시 실행시 데이터베이스에 엔티티 저장

💻 출력

board.id = 1

📝 @SequnceGenerator

  • 속성

    속성기능기본값
    name식별자 생성기 이름필수
    sequenceName데이터베이스에 등록되어 있는 시퀀스 이름hibernate_sequence
    (하이버네이트 기준)
    initialValue◾ DDL 생성 시에만 사용
    ◾ 시퀀스 DDL 생성 시 처음 시작하는 수 지정
    1
    allocationSize시퀀스 한 번 호출에 증가하는 수
    (성능 최적화에 사용)
    50
    catalog,
    schema
    데이터베이스 catalog, schema 이름
  • 매핑할 DDL

  • @GeneratedValue 옆에 사용해도 됨

    💡 SEQUENCE 전략과 최적화

    • 문제점 : 데이터베이스와 2번 통신
      1. 식별자를 구하기 위해 데이터베이스 시퀀스 조회
        ex) SELECT BOARD_SEQ.NEXTVAL FROM DUAL
      2. 조회한 시퀀스를 기본 키 값으로 사용해 데이터베이스에 저장
        ex) INSERT INTO BOARD...

    • allocationSize
      • 시퀀스 접근 횟수 감소 위해 사용
      • 설정한 값만큼 한 번에 시퀀스 값을 증가시킨 후 그만큼 메모리에 시퀀스 값 할당
        ex) allcoationSize = 50
        → 시퀀스 한 번에 50 증가 후 1~50까지 메모리에서 식별자 할당
        → 51에 시퀀스 값 100으로 증가 후 51~100까지 메모리에서 식별자 할당

    • 장점 : 시퀀스 값을 선점하므로 여러 JVM이 동시 동작해도 기본 키 값이 충돌하지 않음
    • 단점 : 시퀀스 값이 한 번에 대량 증가
    • 보완 : 대량 증가를 원치 않고 INSERT 성능 중요치 않을 경우
      allocationSize의 값 1로 설정

    • 적용
      • 과거 : 시퀀스 값을 하나씩 할당받아 allocationSize만큼 사용
        ex) allocationSize = 50
        => 반환된 시퀀스의 값이 '1' → 1~50까지 사용
        => 반환된 시퀀스의 값이 '2' → 51~100까지 사용
      • 현재 : hibernate.id.new_generator_mappingtrue로 설정
        → true로 설정하지 않을 경우 과거 방식으로 최적화

✅ TABLE 전략

  • 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 생성하여 데이터베이스 시퀀스를 흉내내는 전략
    → 모든 데이터베이스에 적용 가능

💻 키 생성 DDL

create table MY_SEQUENCES (
    sequence_name varchar(255) not null ,  //시퀀스 이름으로 사용
    next_val bigint,  //시퀀스 값으로 사용
    primary key ( sequence_name )
)

💻 매핑 코드

@Entity
@TableGenerator(  //테이블 키 생성기 등록
    name = "BOARD_SEQ_GENERATOR",  //테이블 키 생성기 이름
    sequenceName = "MY_SEQUENCES",  //키 생성용 테이블로 매핑
    pkColumnValue = "BOARD_SEQ", allocationSize = 1)
)
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,  //테이블 전략 사용
                    generator = "BOARD_SEQ_GENERATOR"  //테이블 키 생성기 지정
    )						       //id 식별자 값 할당 담당
    private Long id;
    ...
}

💻 사용 코드

private static void logic(EntityManager em) {
    Board board = new Board();
    em.persist(board);
    System.out.println("board.id = " + board.getId());
}
  • SEQUENCE 전략과 내부 동작방식이 같음

💻 출력

board.id = 1
  • 키 생성기 사용할 때마다 next_val 컬럼 값 증가
  • 테이블에 값이 존재하지 않을 경우
    → JPA가 값을 INSERT 하면서 초기화

📝 @TableGenerator

  • 속성
    속성기능기본값
    name식별자 생성기 이름필수
    table키생성 테이블명hibernate_sequence
    (하이버네이트 기준)
    pkColumnName시퀀스 컬럼명sequence_name
    (하이버네이트 기준)
    ValueColumnName시퀀스 값 컬럼명next_val
    (하이버네이트 기준)
    pkColumnValue키로 사용할 값 이름엔티티 이름
    initialValue◾ 초기 값 지정
    ◾ 마지막으로 생성된 값이 기준
    0
    allocationSize시퀀스 한 번 호출에 증가하는 수
    (성능 최적화에 사용)
    50
    catalog,
    schema
    데이터베이스 catalog, schema 이름
    uniqueConstraints
    (DDL)
    유니크 제약 조건 지정

💡 TABLE 전략과 최적화

  • 문제점 : 데이터베이스와 3번 통신
    1. SELECT 쿼리를 사용하여 값을 조회
    2. 조회한 값을 이용하여 데이터베이스에 저장
    3. 다음 값으로 증가시키기 위해 UPDATE 쿼리 사용

  • 해결 방법 : allocationSize 사용하여 최적화

✅ AUTO 전략

  • 선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택

  • @GeneratedValue.starategy의 기본값 = AUTO

💻 매핑 코드

@Entity
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)  // @GeneratedValue와 같음
    private Long id;
    ...
}
  • 데이터베이스 변경시 코드 수정 필요 x
    → 키 생성 전략이 확정되지 않은 개발 초기 단계나 프로토타입 개발 시 사용

  • 선택되는 전략에 따라 대처가 달라짐

    • SEQUNCETABLE 전략 선택된 경우
      → 시퀀스나 키 생성용 테이블 미리 생성 필요
    • 스키마 자동 생성 기능 사용
      → 하이버네이트가 기본값 사용하여 적절한 시퀀스나 키 생성용 테이블 생성


4.4.3 기본 키 매핑 정리

  • 영속성 컨텍스트는 엔티티를 식별자 값으로 구분하므로, 엔티티를 영속 상태로 만들기 위해서는 식별자 값이 반드시 필요함

  • em.persist() 호출 직후

    전략설명
    직접 할당em.persist() 호출 전 애플리케이션에서 직접 식별자 값 할당
    ◾ 식별자 값 존재 x → 예외 발생
    SEQUENCE데이터베이스 시퀀스에서 식별자 값 획득
    → 영속성 컨텍스트에 저장
    TABLE데이터베이스 시퀀스 생성용 테이블에서 식별자 값 획득
    → 영속성 컨텍스트에 저장
    IDENTITY◾ 데이터베이스에 엔티티 저장하여 식별자 값 획득
       → 영속성 컨텍스트에 저장
    ◾ 테이블에 데이터 저장 후 식별자 값 획득 가능

✅ 권장하는 식별자 선택 전략

  • 데이터베이스 기본 키 제약 조건

    1. null값은 허용하지 않는다.
    2. 유일해야 한다.
    3. 변해선 안 된다.
      • 저장된 엔티티의 기본 키 값 절대 변경하지 말 것
      • 변경시 예외 발생 또는 정상 동작 x
  • 기본 키 선택 전략

    • 자연 키(natural key) : 비즈니스에 의미가 있는 키
      ex) 주민등록번호, 이메일, 전화번호 등
    • 대리 키(surrogate key) = 대체 키
      : 비즈니스와 관련 없는 임의로 만들어진 키
      ex) 오라클 시퀀스, auto_increment, 키생성 테이블 사용 등
  • 자연 키보다는 대리 키 사용 권장

    • 비즈니스 요구사항은 변경되기 쉬우나 테이블은 한 번 정의하면 변경하기 어려움
    • 기본 키의 조건을 현재와 미래까지 충족하는 자연 키를 찾기 쉽지 않음
    • 대리 키는 요구사항이 변경되어도 기본 키가 변경되는 일은 드묾
  • 대리 키를 기본 키로 사용하되, 자연 키의 후보가 되는 컬럼들은 필요에 따라 유니크 인덱스 설정하여 사용

profile
기쁘게 코딩하고 싶은 백엔드 개발자

0개의 댓글