JPA/JPA 기본

13. 기본 키 매핑

sdafdq 2023. 10. 21. 23:38

우리가 테이블에서 id에 넣는 그 키

 

그 테이블의 primary key 같은 것 들과 @Entity에서 어떻게 매핑을 할 지

 

id를 직접 넣어주고 싶다,

@Id만.

 

DB가 자동으로 Generate 해주는 것을 쓰고 싶다,

@GeneratedValue를 추가해 줌.

 

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

전략 = 옵션

이렇게 넣는다.

default가 AUTO이다.

AUTO는 DB마다 좀 다르다. 아마 하이버네이트가 기본으로 설정한? 그런 건가 보다. 방언마다 다르다.

확인 해 보고 써야 함.

 

여튼 저게 기본값이고,

실상은 3개이다.

 

IDENTITY

SEQUENCE

TABLE

 

AUTO는 저 셋중 하나를 쓰는거다.

 

IDENTITY

먼저 IDENTITY는 기본키 생성을 DB에 위임하는 것이다.

예를 들어 AUTO_INCREMENT.

참고로 DB에 위임하는 것인 만큼, 이거 쓸려면 id에 auto_increment같은 옵션이 달려 있어야 함.

 

참고로 DDL, 즉 테이블 자동으로 생성되도록 두면,

id에 auto_increment 옵션이 붙으면서 테이블이 생성됨. 방언마다 약간 다르긴 함.

 

근데, 

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

em.persist(member);

이 auto_increment는 저거 persist 하면 바로 쿼리를 날린다.

왜냐하면 보통 persist 하면 영속성 컨텍스트에 들어갔다가 트랜잭션 commit 시점에 쓰기지연 SQL 저장소에 있는 쿼리들을 모아서 날리는 데, 

영속성 컨텍스트에서는 식별을 ID로 한다.

근데 ID를 알려면 DB에 값을 넣어서 매겨진 id로 얻어오는 수 밖에 없다.

 

그래서, DB에 개인키 생성 전략을 위임하는 IDENTITY의 경우 persist 순간에 바로 insert 쿼리를 날린다.

그럼 select 문도 날려야 하는 거 아닌가? 라는 생각이 당연히 드실텐데,

JDBC 드라이버 내부적으로 insert 쿼리 날리면서 동시에 id를 가져올 수 있는 그런 로직이 있다고 한다. 자세한 건 모른다.

얘만 예외적으로 persist 순간에 그렇다.

 

 

 

SEQUENCE

먼저 DB의 시퀀스 자체에 대해 이야기 하겠다.

auto_increment는 테이블에 종속되어 지는 옵션이다.

 

반면, 시퀀스는 테이블에 종속되어지는 게 아니라 DB에 종속된다.

즉, 여러 테이블이 공유하면서 사용할 수 있는 거다.

 

MySQL은 auto_increment를 주요 키 전략으로 쓰고,

오라클은 시퀀스를 주요 키 전략으로 사용하는데,

 

이것은 오라클이 여러 테이블에 데이터가 있지만, 그 데이터 끼리의 연결성을 나타내기 위해 공유되는 id를 쓰고 싶어 그런 것이다. 즉, 테이블은 달라도, 같은 id를 쓰게 하며 데이터끼리의 연결성을 나타낼 수 있다.

 

다시 돌아와서, 

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

이렇게 SEQUENCE 전략을 쓰게 되면 

IDD를 사용하는 경우 자동으로 create sequence 해서 시퀀스를 만든다.

create sequence Member_SEQ start with 1 increment by 50

이렇게 자동으로 쿼리를 만들어 보낸다.

여튼 IDD할 떄 이런 것이고,

Hibernate: 
    select
        next value for Member_SEQ
Hibernate: 
    /* insert for
        hellojpa.Member */insert 
    into
        Member (name,id) 
    values
        (?,?)

persist 하면

이렇게 먼저 시퀀스에서 값을 가져온 다음, 

그걸 멤버 객체에 넣고,

그 멤버 객체를 영속성 컨텍스트에 넣는다.

그 뒤는 같다.

 

@Entity
@SequenceGenerator(name = "member_seq_generator", sequenceName = "member_seq")
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "member_seq_generator")
    private Long id;
    
    
}

이렇게 시퀀스의 이름을 지정해서 사용할 수 있다.

sequenceName은 db에서 가져올 DB의 시퀀스 이름이고,

name은 내가 여기 코드상에서 접근할 이름이다.

 

저렇게 전략을 시퀀스로 SEQUENCE로 등록해 놓고, 계산방법은 저 @SequenceGenerator에서 등록한 걸 가져오면 된다.

 

기본은 그렇고, 이 시퀀스 전략에 내부적으로 최적화 되어있는게 있다.

이렇게, 시퀀스 전략을 쓰면, DB의 시퀀스로부터 값을 얻어오는데, 

미리 한 50개 쯤 가져 온다.

next value for member_seq

이렇게 쿼리를 날려서 값을 가져와야 하는데, DB의 시퀀스를 아예 50개 채워서 51이라고 만들어 놓고 

1~ 50까지를 이 한 트랜잭션 안에서 사용 하는거다.

이렇게 하는 이유는 예를 들어,

em.persist(member1);
em.persist(member2);
em.persist(member3);

이렇게 3번 persist 하면 추가적으로 DB에

next value for member_seq
next value for member_seq
next value for member_seq

이렇게 해야 하므로, 그냥 한번에 50까지 땡겨오는 거다.

그래서 한 트랜잭션이 끝난 다음, 다음 트랜잭션으로 저장해 보면,

그 데이터의 id는 52이었나 그 때부터 시작한다.

이게 처음 시퀀스를 쓰면 next value for member_seq 이걸 한번 호출하고, 

그 뒤에 next value for member_seq 다시 또 호출하여 본격적으로 시퀀스를 사용하기 시작한다.

뭔가 좀 처음 만든 후에는 할 것이 있나보다.. 그래서 next value for member_seq를 한번 하고 시작하나 보다..

자세한 것은 잘 모르겠다..

이게 왜냐하면 영상에서는 분명 아무것도 저장 안하면 -49였나 그러고 처음 호출했을 때 1을 만들기 위해서 그런 거 같은데,

나는 그냥 1부터 시작이다. 뭔가 버그가 있는 듯 하다.

여튼, 50씩 한번에 땡겨와서, DB에서도 시퀀스를 50올려놓고, 이 트랜잭션 안에서 1~50까지 쓰는거다.

다음 트랜잭션은 그럼 시퀀스 51~101 이렇게 사용한다.

일단 그렇게 알고 있자. 지금 버그가 있는 듯 하다.

https://www.inflearn.com/questions/660327

 

MEMBER_SEQ를 2번 호출 하는 이유 - 인프런 | 질문 & 답변

처음 호출 하면 51개로 맞추고 그 다음부터, 메모리에서 사용한다고 하는데 이렇게 하는 이유가 무엇인가요 ?처음 호출 할때 50개를 미리 세팅 하고 1번부터 사용하면 안되나요??? - 질문 & 답변 |

www.inflearn.com

50씩 증가하는 이유는 저 allocationSIze가 한번에 가져오는 수인데, 저게 50이라.

 

 

 

 

 

TABLE

키 생성 전용 테이블을 하나 만들어서, 시퀀스 전략을 흉내내는 전략이다.

장점은 어떤 DB는 auto_increment고 어떤 db는 시퀀스이다.

근데 이 테이블 전략은 아예 테이블 만들고 관리하는거라 모든 DB에 적용이 가능하다.

하지만, 별도의 테이블을 사용하다 보니 락이 걸리는 둥 성능 이슈가 있을 수 있다.

시퀀스나 이런 건 따로 숫자 뽑는데 최적화 되어 있는데, 얘는 그냥 테이블이니..

 

그냥 시퀀스랑 비슷하게 쓴다고 보면 된다.

@Entity
@TableGenerator(name = "member_seq_generator", table = "member_seq")
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "member_seq_generator")
    private Long id;

}

이거 보면

seq 테이블 생성하고, (처음에만, dll.auto가 create니까)

처음이라 그런지

insert into member_seq(sequence_name, next_val) values ('Member',0)

해서 값 넣어주고,

뭐 여기까진 처음에 한번만 할 거니까 괜찮다.

그런데..

 

그 이후

Hibernate: 
    select
        tbl.next_val 
    from
        member_seq tbl 
    where
        tbl.sequence_name=? for update
Hibernate: 
    update
        member_seq 
    set
        next_val=?  
    where
        next_val=? 
        and sequence_name=?
Hibernate: 
    /* insert for
        hellojpa.Member */insert 
    into
        Member (name,id) 
    values
        (?,?)

아마 값을 가져와서, +1해서 메모리에 가지고 있고, 업데이트도 해 주고, 그 다음 member에 넣어서 insert

 

성능 이슈가 있을 것 같다...

잘 안쓸 것 같아, 설명은 하지 않고 넘어가겠다.

 

 

 

권장하는 식별자 전략

기본키 제약 조건 : not null, 유일, 변하면 안됨.

그러므로 주민번호같은거라도 그런 비즈니스키 말고, 대리키를 쓰길..

권장은 Long형 + 대체키 + 키 생성전략(auto_increment 등)

 

Integer도 있는데 Long을 쓰는 이유는,

물론 저장공간은 2배를 쓰겠지만, 

그것보다 타입을 바꾸는 것이 더 힘들다.

요새 하드웨어는 이정도는 괜찮아서, 그냥 Long을 쓰는 게 좋다.

 

'JPA > JPA 기본' 카테고리의 다른 글

15. 연관관계 매핑  (0) 2023.10.22
14. 예제  (0) 2023.10.22
12. 필드와 컬럼 매핑  (0) 2023.10.21
11. DB 스키마 자동 생성  (0) 2023.10.21
10. 객체와 테이블의 매핑  (0) 2023.10.21