JPA 엔티티 매핑 - 기본 키 매핑

2022. 7. 1. 00:47·공부/JPA
반응형

출처 : 자바 ORM 표준 JPA 프로그래밍 인프런 강의

 

JPA 게시글은 대부분 인프런의 김영한님의 강의인 '자바 ORM 표준 JPA 프로그래밍' 기반으로 내용을 정리했습니다.

 

기본 키 매핑

기본키의 경우엔 직접 할당과 자동생성 방법이 있다. 직접 할당은 여태 써온 @Id 애노테이션을 사용하면 된다.

직접 할당

  • @Id
// Member.class 
@Id
private Long id;


// 실행 클래스
...생략
Member member = new Member();
member.setId(1000L); // 기본키 직접 할당
member.setUsername("B");
...생략

이렇게 개발자가 직접 PK 값을 할당해주는 경우 @Id만 사용하면 된다.

 

자동 생성(@GeneratedValue)

자동 생성의 경우 IDENTITY, SEQUENCE, TABLE, AUTO 속성이 있다.

IDENTITY 전략

  • 기본키 생성을 데이터베이스에 위임한다.
  • 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용한다.
  • JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL을 실행한다.
  • AUTO_INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있다.
  • IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회한다.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;


// 실행 클래스
...생략
Member member = new Member();
member.setUsername("B");

em.persist(member);
...생략


// 출력 로그
create table Member (
   id bigint generated by default as identity,
    name varchar(255) not null,
    primary key (id)
)

/* insert hellojpa.Member
    */ insert 
    into
        Member
        (id, name) 
    values
        (null, ?)

객체 생성 시 id 값을 따로 세팅하지 않아도 자동으로 insert문에 id 데이터가 저장되어 INSERT SQL이 전달된다.

IDENTITY은 위에 적혀있기도 하지만 중요한 이야기니 다시 얘기해보자면 id 값이 JPA에서 저장되는게 아니라, JPA INSERT SQL을 날릴 때 id가 null 값으로 되어있으면 DB에서 id 값을 세팅해준다. 즉 id 값을 알 수 있는 시점은 DB에 저장이 되고나서야 알 수 있다는 것이다. 이게 왜 문제가 될까? JPA에는 영속성 컨텍스트가 존재한다. 데이터가 영속성 컨텍스트에서 관리가 되려면 무.조.건 id 값이 존재해야한다. 그래서 이 문제를 해결하기 위해 특별히 IDENTITY 전략은 기존 매커니즘과 다르게 동작한다.

일단 transaction.commit() 시점이 아니라 em.persist()가 실행되는 시점에 INSERT SQL문이 전달된다. 그리고 JPA가 DB에 저장되어 있는 id 값을 다시 SELECT해서 가져와 영속성 컨텍스트에 id값을 넣어 관리한다. 

결론은 그래서 IDENTITY 전략에서는 SQL을 모아서 쿼리를 한 번에 날리는 버퍼링 기능을 사용할 수 없다.

 

SEQUENCE 전략

  • 데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트이다.
  • 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용한다.
  • 참고로 필드 데이터 타입은 int에는 0이 포함되어있기 때문에 별로고 Integer를 사용하는게 더 낫다. 근데 또 Integer보다 Long을 사용하는게 더 낫다.
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;


// 출력 로그

Hibernate: 
    call next value for hibernate_sequence //시퀀스 먼저 호출 후 INSERT SQL 실행
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (name, id) 
        values
            (?, ?)

참고로 시퀀스에 관한 별도 설정이 없으면 기본 시퀀스인 hibernate_sequence를 사용한다. 만약 테이블마다 시퀀스를 따로 관리하고 싶다면 @SequenceGenerator를 사용해서 매핑을 하면된다.

  • @SequenceGenerator 사용

hibernate.hbm2ddl.auto 값을 create로 설정한다.

@Entity
@SequenceGenerator(
        name = "MEMBER_SEQ_GENERATOR",
        sequenceName = "MEMBER_SEQ", // 매핑할 데이터베이스 시퀀스 이름
        initialValue = 1, allocationSize = 1)
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "MEMBER_SEQ_GENERATOR")
    private Long id;

...생략


// 출력 로그
Hibernate: 
    call next value for MEMBER_SEQ
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (name, id) 
        values
            (?, ?)
  • @SequenceGenerator 속성
    • name : 식별자 생성기 이름. 필수값이다.
    • sequenceName : 데이터베이스에 등록되어 있는 시퀀스 이름. 기본값은 hibernate_sequence
    • initialValue : DDL 생성 시에만 사용된다. 시퀀스 DDL을 생성할 때 처음으로 시작하는 수를 지정한다. 기본값은 1이다.
    • allocationSize : 시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용)이다. 데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값을 반드시 1로 설정해야 한다. 기본값은 50이다.
    • catalog, schema : 데이터베이스 catalog, schema 이름이다.

 

SEQUENCE 전략의 경우에도 IDENTITY 전략과 똑같이 DB에 가기 전까지는 id 값을 알 수 없다. 대신 IDENTITY 전략과 조금 다른 점이 있는데 SEQUENCE 전략은 em.persist()가 실행 될 때  시퀀스를 호출해 id 값을 가져와 객체에 id를 세팅하고나서 영속성 컨텍스트에 저장한다. 즉 INSERT SQL을 보내기 전에 id 값을 세팅한다. 그러기 때문에 IDENTITY 전략에서 사용하지 못하는 버퍼링 기능을SEQUENCE 전략에서는 사용할 수가 있다.

그럼 성능에는 아무 영향이 없을까?

뭔가 SEQUENCE 전략이나 IDENTITY 전략은 INSERT SQL를 보내기 전이나 후에 굳이 DB에 계속 네트워크를 날리면 성능이 떨어지지 않을까?라는 생각을 할 수 있다. 그래서 그 해결 방법으로 allocationSize라는 속성을 JPA에서 제공한다.

allocationSize는 시퀀스를 한 번 호출할 때 지정한 값만큼 미리 데이터베이스에 미리 적재시켜놓고 메모리에서는 하나하나씩 데이터를 쌓아간다. 그러다가 데이터베이스에 미리 적재시켜 놓은 개수와 메모리에 쌓인 개수가 같아지면 또 Next Call을 한 번 호출해서 데이터베이스에는 50~100번까지의 50개의 데이터를 미리 적재시켜 놓고 메모리에서는 51~100번까지 데이터를 담도록한다. 

또 allocationSize 기능이 너무 똑똑한 나머지 여러 WAS에서 동시에 실행시켜도 동시성 이슈 없이 잘 진행이 된다.

// 객체 클래스
@Entity
@SequenceGenerator(
        name = "MEMBER_SEQ_GENERATOR",
        sequenceName = "MEMBER_SEQ",
        initialValue = 1, allocationSize = 50) // 50으로 설정
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
            generator = "MEMBER_SEQ_GENERATOR")
    private Long id;
... 생략


// 실행 클래스
... 생략
Member member1 = new Member();
member1.setUsername("A");

Member member2 = new Member();
member2.setUsername("B");

Member member3 = new Member();
member3.setUsername("C");

// DB 시퀀스 : 1    | 필드 id : 1
// DB 시퀀스 : 51   | 필드 id : 2
// DB 시퀀스 : 51   | 필드 id : 3

em.persist(member1); // 1, 51
em.persist(member2); // 메모리
em.persist(member3); // 메모리

tx.commit();
... 생략

allocationSize의 적정 값은 50~100정도를 권장한다. 값이 너무 크면 웹 애플리케이션이 다시 구동될 때 전에 사용하지 못한 크기 만큼이 낭비되므로 주의해야한다.

 

TABLE 전략

키 전용 생성 테이블을 하나 만든다. 데이터베이스 시퀀스를 흉내내는 전략이다. 장점으로는 모든 데이터베이스에 적용이 가능하고 단점은 테이블을 직접 접근해서 성능 상의 이슈가 날 수도 있고, 또한 테이블에 락이 걸릴 수 있다는 단점을 가지고 있다.

hibernate.hbm2ddl.auto의 속성을 create로 변경한다.

// 객체 클래스
@Entity 
@TableGenerator(
        name = "MEMBER_SEQ_GENERATOR",
        table = "MY_SEQUENCES",
        pkColumnValue = "MEMBER_SEQ", allocationSize = 1)
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
                    generator = "MEMBER_SEQ_GENERATOR")
    private Long id;

그럼 MY_SEQUENCES 테이블이 생성되고 테이블 안에는 내가 만든 시퀀스와, 그 시퀀스의 다음 값을 저장하고 있다.

TABEL 전략은 잘 사용하지 않는다.

  • TableGenerator - 속성
    • name : 식별자 생성기 이름. 필수값이다
    • table :  키 생성 테이블명 기본값은 hibernate_sequences이다.
    • pkColumnName : 시퀀스 컬럼명. 기본값은 sequence_name이다.
    • valueColumnNa : 시퀀스 값 컬럼명. 기본값은 next_val이다.
    • pkColumnValue : 키로 사용할 값 이름. 기본값은 엔티티 이름이다.
    • initialValue : 초기 값, 마지막으로 생성된 값이 기준이다.
    • allocationSize : 시퀀스 한 번 호출에 증가하는 수 (성능 최적화에 사용). 기본값은 50이다.
    • catalog, schema : 데이터베이스 catalog, schema 이름을 주면 된다.
    • uniqueConstraints(DDL) : 유니크 제약 조건을 걸 수 있다.

 

권장하는 식별자 전략

  • 기본키 제약 조건 : NOT NULL, 유일, 변하면 안된다.
  • 미래까지 이 조건을 만족하는 자연키는 찾기 어려우니 대리키(대체키)를 사용하자.
    • 대리키 : 비즈니스와 전혀 상관없는 키 (uuid나 auto_increment 등)
    • ex) 주민등록번호 기본키로 적절하지 않다.
  • 권장 : Long형 + 대체키 + 키 생성전략 사용
반응형
저작자표시 비영리 변경금지 (새창열림)
'공부/JPA' 카테고리의 다른 글
  • JPA 연관관계 매핑 - 단방향 연관관계
  • JPA 엔티티 매핑 - 요구사항 분석과 기본 매핑 (실전 예제)
  • JPA 엔티티 매핑 - 필드와 컬럼 매핑
  • JPA 엔티티 매핑 - 객체와 테이블 매핑, 데이터베이스 스키마 자동 생성
데부한
데부한
어차피 할 거면 긍정적으로 하고 싶은 개발자
    반응형
  • 데부한
    동동이개발바닥
    데부한
  • 전체
    오늘
    어제
    • 분류 전체보기 (307)
      • 방통대 컴퓨터과학과 (27)
        • 잡담 (9)
        • 3학년1학기 (17)
      • 프로젝트 및 컨퍼런스 회고 (1)
        • 프로젝트 (4)
        • 한이음 프로젝트 (0)
        • 회고 (3)
      • 공부 (165)
        • Spring (37)
        • JPA (71)
        • 인프런 워밍업 클럽_BE (10)
        • Java (6)
        • React.js (27)
        • 넥사크로 (11)
        • 기타 (3)
      • 알고리즘 (85)
        • 알고리즘 유형 (10)
        • 알고리즘 풀이 (57)
        • SQL 풀이 (18)
      • 에러 해결 (13)
      • 잡담 (7)
        • 국비교육 (2)
        • 구매후기 (5)
        • 진짜 잡담 (0)
  • 블로그 메뉴

    • Github
    • Linkedin
    • 홈
    • 방명록
    • 글쓰기
    • 관리
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    전자정부프레임워크
    Spring
    프로그래머스
    에러해결
    QueryDSL
    백준
    IT
    MSA
    egov
    코딩테스트
    RESTful
    인프런
    알고리즘
    자바스크립트
    개발자
    SpringBoot를 이용한 RESTful Web Service 개발
    Java
    JPA
    SQL
    토비의스프링부트
    프론트엔드
    springboot
    react
    oracle
    방통대
    운영체제
    넥사크로
    스프링부트
    토이프로젝트
    기출문제
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
데부한
JPA 엔티티 매핑 - 기본 키 매핑
상단으로

티스토리툴바