티스토리 뷰

dev_공부일지/JPA 기본

기본 키 매핑

dev_0hoon 2024. 1. 31. 16:45

 

Id를 직접 할 당할 경우 : @Id

 

Id를 자동 생성 할 경우 @GeneratedValue (쉬운 예로 db의 Auto로 시퀀스 값을 올려주는 기능)

IDENTITY : 데이터베이스에 위임, MYSQL (나는 잘 모르겠고 db야 너가 알아서 해주라 라는 느낌)

 > 기본 키 생성을 데이터베이스에 위임

 > 주로 MySql, PostgreSQL, SQL server, DB2에서 사용 (예: MySQL의 AUTO_INCREMENT)

 > JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행

 > AUTO_INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있음

 > DENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행 하고 DB에서 식별자를 조회

= IDENTITY전략은 db에 도착했을 때에 id가 null일 경우에 값이 자동으로 부여된다. 즉 id값을 알 수 있는 시점은 db에 저장이 되고 나서이다. jpa의 영속성 컨텍스트를 생각해봤을 때, 하나의 요청인 한개의 로직상태가 커밋 되기 전에는 id값을 알 수가 없다.

 

그래서 특이하게 작동하게 된다. 기존 영속성 컨텍스트와 부관하게, @GeneratedValue를 IDENTITY로 사용한 경우 

            Member member = new Member();
            member.setRoleType(RoleType.USER);

            System.out.println("============");
            entityManager.persist(member);
            System.out.println("memeber.id = " + member.getId());
            System.out.println("============");
            ts.commit();

실행로그

============
Hibernate: 
    /* insert helloJpa.Member
        */ insert 
        into
            Member
            (id, age, createdDate, description, lastModifiedDate, roleType, testLocalDate, testLocalDateTime, name) 
        values
            (null, ?, ?, ?, ?, ?, ?, ?, ?)
memeber.id = 1
============

 

commit 단계가 아닌 persist 단계에서 쿼리를 날려버린다.

또한 jpa 내부에서는 인서트 쿼리를 날린 후 자동으로 return을 해주기에 바로 member.id를 찾을 수 있다.

 

 

 

SEQUENCE : 데이터베이스 시퀀스 오브젝트 사용, ORACLE

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;

 ㄴ @SequenceGenerator 필요

    > 데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트 (예: 오라클 시퀀스)

    > 오라클 , PostgreSQL, DB2, H2 데이터베이스에서 사용

+ SEQUENCE 전략 - 매핑

@Entity
@SequenceGenerator(
name = “MEMBER_SEQ_GENERATOR",
sequenceName = “MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
initialValue = 1, allocationSize = 1)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
private Long id;

 

이렇게 전략을 주면

create sequence member_seq start with 1 increment by 50

로 만들게 된다.

-속성

ㄴ name : 식별자 생성기 이름 (기본값: 필수)

ㄴ sequenceName : 데이터베이스에 등록되어 있는 시퀀스 이름 (기본값 : hibernate_sequence)

ㄴ initialValue : DDL 생성 시에만 사용됨, 시퀀스 DDL을 생성할 때 처음 1 시작하는 수를 지정한다 (기본값 1)

ㄴ allocationSize : 시퀀스 한 번 호출에 증가하는 수 (성능 최적화에 사용됨, 데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값을 반드시 1로 설정해야 한다.) (기본값 : 50)

ㄴ catalog, schema : 데이터베이스 catalog, schema 이름

 

시퀀스 전략은 id값을 찾을 때 조금 다른게 작동한다.

============
Hibernate: 
    call next value for member_seq_generator
Hibernate: 
    call next value for member_seq_generator
memeber.id = 1
============
Hibernate: 
    /* insert helloJpa.Member
        */ insert 
        into
            Member
            (age, createdDate, description, lastModifiedDate, roleType, testLocalDate, testLocalDateTime, name, id) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?)
@SequenceGenerator(name = "member_seq_generator", sequenceName = "member_seq")
public class Member {

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

저장이 되기 전에 먼저 시퀀스, 즉 id 값을 찾는다면 @SequenceGenerator에서 발생한 시퀀스 값이 먼저 인서트 쿼리에 추가가 되이있기 때문에 그 값을 찾아준다.

 

sequence 전략에는 특이점이 있다. sequence 자동 생성값이 한번에 50이 디폴트인 것이다. 요청시 한번에 50을 늘려주고 메모리에서 1,2,3... 순으로 사용하다가 50이 되는 순간 다시 50을 늘려준다. 그렇게 사용해서 비효율적인 요청이 줄어든다.

@SequenceGenerator(name = "member_seq_generator", sequenceName = "member_seq",
                    initialValue = 1, allocationSize = 50)//50이 기본값
public class Member {

 

            Member member1 = new Member();
            member1.setUsername("a");

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

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

            System.out.println("==========");

            entityManager.persist(member1); // db seq 1 | id 1
            entityManager.persist(member2); // db seq 51 | id 2
            entityManager.persist(member3); // db seq 51 | id 3

            System.out.println("member1 = " + member1.getId());
            System.out.println("member2 = " + member2.getId());
            System.out.println("member3 = " + member3.getId());

            System.out.println("==========");
            ts.commit();

 

로그

==========
Hibernate: 
    call next value for member_seq // 첫 1번 생성
Hibernate: 
    call next value for member_seq // 50 생성
member1 = 1
member2 = 2 //여기서 부터 50까지는 seq를 올리는 요청이 없다.
member3 = 3
==========
Hibernate: 
    /* insert helloJpa.Member
        */ insert 
        into
            Member
            (age, createdDate, description, lastModifiedDate, roleType, testLocalDate, testLocalDateTime, name, id) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    /* insert helloJpa.Member
        */ insert 
        into
            Member
            (age, createdDate, description, lastModifiedDate, roleType, testLocalDate, testLocalDateTime, name, id) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    /* insert helloJpa.Member
        */ insert 
        into
            Member
            (age, createdDate, description, lastModifiedDate, roleType, testLocalDate, testLocalDateTime, name, id) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?)

allocationSize는 TableGenerator 전략도 동일하다.

 

TABLE : 키 생성용 테이블 사용, 모든 DB에서 사용

 ㄴ @TableGenerator 필요

> 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략

> 장점 : 모든 데이터베이스에 적용가능

> 단점 : 성능 

@Entity
@TableGenerator(name = "MEMBER_SEQ_GENERATOR", table = "MY_SEQUENCES",
        pkColumnValue = "MEMBER_SEQ" , allocationSize = 1)
//@SequenceGenerator(name = "member_seq_generator", sequenceName = "member_seq")
public class Member {

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

다른 시퀀스 전략을 따라한다는 것은 로그를 보면 알 수 있다.

Hibernate: 
    
    create table Member (
       id bigint not null,
        age integer,
        createdDate timestamp,
        description clob,
        lastModifiedDate timestamp,
        roleType varchar(255),
        testLocalDate date,
        testLocalDateTime timestamp,
        name varchar(255),
        primary key (id)
    )
Hibernate: 
    
    create table MY_SEQUENCES (
       sequence_name varchar(255) not null,
        next_val bigint,
        primary key (sequence_name)
    )
Hibernate: 
    
    insert into MY_SEQUENCES(sequence_name, next_val) values ('MEMBER_SEQ',0)
   
   Hibernate: 
    select
        tbl.next_val 
    from
        MY_SEQUENCES tbl 
    where
        tbl.sequence_name=? for update
            
Hibernate: 
    update
        MY_SEQUENCES 
    set
        next_val=?  
    where
        next_val=? 
        and sequence_name=?
Hibernate: 
    /* insert helloJpa.Member
        */ insert 
        into
            Member
            (age, createdDate, description, lastModifiedDate, roleType, testLocalDate, testLocalDateTime, name, id) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?)

시퀀스 테이블을 만들 뒤에 그 값을 넣고 올리고 있다.

 

* 운영에서는 부담되는 전략이다. db가 제공하는 걸 사용하는 것이 좋음

>속성

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

 

 

- AUTO : 방언에 따라 자동 지정, 기본값

 

 

권장하는 식별자 전략자

- 기본 키 제약 조건 : null 아님, 유일, 변하면 안된다.

- 미래까지 이 조건을 만족하는 자연키(전화번호, 주민번호 등)는 찾기 어렵다. 대리키(대체키)(랜덤값)를 사용하자.

- 예를 들어 주민등록번호도 기본키로 적절하지 않다.

- 권장 : Long형 + 대체키 + 키 생정전략 사용

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함