Spring Model/02. 객체 매핑과 P.C

04.자동 키 매핑 전략

  • -

게시글의 번호처럼 자동으로 증가하는 컬럼을 위해서는 sequence를 사용하거나 auto_increment 속성을 이용한다. 이번 포스트에서는 이렇게 자동으로 생성되는 키를 매핑하는 방법에 대해 알아보자.

 

@Id의 자동 키 매핑 전략

일반적으로 식별자 컬럼(P.K)은 일반 컬럼이 아닌 자동 생성되는 대리키를 이용하는 경우가 많다. 이는 고유성이 쉽게 보장되고 인덱스 성능을 향상시키는 등의 장점이 있기 때문이다. 게시글의 번호나 회원의 번호등은 아주 좋은 예이다. 이런 자동 생성 대리키를 이용하기 위해서 Sequence를 사용하거나 auto_increment 속성을 이용할 수 있다.

JPA에서는 자동으로 증가하는 P.K를 설정하기 위해 @GeneratedValue를 사용한다.

@GeneratedValue

@GeneratedValue는 주로 정수 타입의 @Id 속성에 대해 insert 되는 시점에 자동으로 값을 할당한다.

@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface GeneratedValue {
    GenerationType strategy() default AUTO;
    String generator() default "";          // SEQUENCE나 TABLE에서 사용된다.
}

@GeneratedValue의 속성인 strategy는 GenerationType enum에 4가지로 선언되어 있다.

  • GenerationType.SEQUENCE: sequence를 지원하는 Oracle, H2, DB2, PostgreSQL에서 사용할 수 있는 전략으로 @SequenceGenerator를 이용해서 sequence를 설정한다.
  • GenerationType.IDENTITY: H2, MySql, SQL Server, PostgreSQL처럼 auto_increment 속성을 지원하는 경우에 적용한다. 코드적으로 가장 간단하다.
  • GenerationType.TABLE: 별도의 Sequence 관리 테이블을 이용해서 사용한다. 이 방법은 데이터베이스 독립성을 높일 수는 있지만 성능은 떨어질 수 있다.
  • GenerationType.AUTO: 기본 생성 전략으로 DB에 따라 적절한 방법을 선택한다. 예를 들어 H2나 Oracle의 경우는 GenerationType.SEQUENCE를 사용하고 MySql의  경우는 GenerationType.IDENTITY를 사용한다.

여기서는 SEQUENCE와 IDENTITY에 대해 살펴보자.

 

GenerationType.SEQUENCE

GenerationType.SEQUENCE는 DB의 시퀀스를 이용하며 Oracle, DB2, H2 등에서 사용할 수 있다. 이 전략을 사용하려면 먼저 DB에 있는 시퀀스를 참조하거나 @SequenceGenerator를 이용해서 시퀀스를 직접 생성할 수 있다. 사용하려는 시퀀스의 이름을 @GeneratedValue의 generator에 설정하면 된다.

@Target({ TYPE, METHOD, FIELD })
public @interface SequenceGenerator {
  // SequenceGenerator의 유니크한 이름으로 GeneratedValue에서 참조됨
  String name(); 			
  // DB에 등록되는 시퀀스의 이름으로 생략 시 hibernate_sequence 사용
  String sequenceName() default ""; 
  // 생성되는 시퀀스의 초기 값
  int initialValue() default 1; 	
  // 시퀀스 호출 시 증가하는 값, 최적화를 위하 DB에서 50씩 증가하고 메모리에서 1씩 증가
  int allocationSize() default 50; 	
}

활용 예를 살펴보기 전에 자동으로 매핑되는 키를 썼을 때와 그렇지 않을 경우를 비교하기 위해 기존에 작성했던 MemberRepoTest 클래스의 extendsTest()을 다시 한번 실행해서 실제 동작하는 쿼리를 살펴보자.

# 추가 대상 사용자: mno = 100L
PaidMember.builder().mno(100L).id("id").name("name").pass("pass12").build();

Hibernate: #mno=100인 사용자를 조회해봄: 키 중복 방지
    select
        pm1_0.mno,. . .
    from
        paid_member pm1_0 
    join
        member pm1_1 
            on pm1_0.mno=pm1_1.mno 
    where
        pm1_0.mno=?
21:39:53 [TRACE] [o.h.orm.jdbc.bind.logBinding.24] binding parameter (1:BIGINT)<-[100]

Hibernate: # 중복되지 않은 경우 member 테이블에 insert 실행
    insert 
    into
        member
        (create_time, email_domain, email_id, gender, id, name, pass, mno) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?)

Hibernate: # 다음으로 paid_member에도 추가
    insert 
    into
        paid_member
        (paid_date, payment_method, mno) 
    values
        (?, ?, ?)

여기서 눈여겨볼 부분특정한 값을 P.K로 사용하는 경우 키 중복을 방지하기 위해서 미리 한번 조회해보고 해당 키를 사용하는 데이터가 없을 경우에 insert 를 수행한다는 점이다.

이제 Member Entity에 @GeneratedValue를 적용해보자.

@Entity
@Table(name = "member")
@Inheritance(strategy = InheritanceType.JOINED)
public class Member extends BaseEnttiy {
    public enum Gender {
        M, F
    }

    @Id
    @SequenceGenerator(name="member_no_seq", sequenceName = "member_seq")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "member_no_seq")
    private Long mno;
    . . .
}

이제 mno는 @GeneratedValue가 추가되었으므로 member_seq를 통해 자동으로 채번된다. 따라서 테스트 과정에서 mno를 세팅하는 부분은 필요 없어진다.

// PaidMember.builder().mno(100L).id("id").name("name").pass("pass12")...build();
PaidMember.builder().id("id").name("name").pass("pass12")....build();

위와 같이 테스트 코드를 수정 후 테스트를 수행해보자. 테스트는 당연히 통과해야 하며 우리의 관심사는 실행되는 SQL이다.

# sequence 생성
create sequence member_seq start with 1 increment by 50

# DDL
select  next value for member_seq                     # 시퀀스를 이용한 채번
insert  into member (create_time, email_domain, email_id, gender, id, name, pass, mno) 
             values (?, ?, ?, ?, ?, ?, ?, ?)

insert  into paid_member (paid_date, payment_method, mno) 
             values (?, ?, ?)

실행 결과를 살펴보면 시퀀스를 만드는 부분이 있고 DDL이 실행되는데 insert 하기 전에 테이블을 조회하는 부분이 없다. mno는 시퀀스에 의해 채번되기 때문에 값이 중복될 염려가 없기 때문이다.

 

GenerationType.IDENTITY

GenerationType.IDENTITY는 auto_increment에 해당하는 기능을 제공하는 H2, MySql, SQL Server, PostgreSQL 등에서 사용 가능하다.

Member 엔티티의 GeneratedValue 속성을 다음과 같이 변경해보자.

@Entity
@Table(name = "member")
@Inheritance(strategy = InheritanceType.JOINED)
public class Member extends BaseEnttiy {
    @Id
    //@SequenceGenerator(name="member_no_seq", sequenceName = "member_seq")
    //@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "member_no_seq")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long mno;
}

코드가 훨씬 단순해졌다. 다시 테스트를 실행하고 생성되는 SQL을 검토해보자. 일단 DDL에서 member의 mno에 generated by default as identity가 추가된 것을 볼 수 있다.

Hibernate: 
    create table member (
        create_time timestamp(6),
        mno bigint generated by default as identity,
        . . .
        primary key (mno)
    )

그리고 데이터가 추가되는 과정에서도 별도로 시퀀스를 채번하지 않고 깔끔하게 처리되는 것을 볼 수 있다.

Hibernate: 
insert into  member (create_time, email_domain, email_id, gender, id, name, pass, mno) 
             values (?, ?, ?, ?, ?, ?, ?, default)

Hibernate: 
insert into paid_member (paid_date, payment_method, mno) 
                 values (?, ?, ?)

앞으로의 예에서는 IDENTITY 전략을 사용하도록 하자.

 

다른 엔티티와 레포지터리 작성

 

Entity 작성

이제까지의 내용을 고려하여 게시판을 만들기 위해 필요한 엔티티들을 구성해보자. Post, Reply, Attachment는 모두 com.doding.board.post.model.entity 패키지에 작성한다.

Entity에 대한 class Diagram

 

Repository 작성

위에서 작성한 Post, Reply, Attachment에 대한 Repository를 각각 com.doding.board.post.model.repo 패키지에 작성한다.

 

'Spring Model > 02. 객체 매핑과 P.C' 카테고리의 다른 글

06. 엔티티의 상태 관리  (0) 2022.04.12
05. EntityManager와 Persistence Context  (0) 2022.03.19
03. Value Type  (0) 2020.06.07
02. OR-Mapping과 상속  (0) 2020.06.02
01. Hello Spring Data JPA  (0) 2020.05.30
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.