당니의 개발자 스토리
기본 키 매핑 본문
기본 키 매핑
기본 키 맵핑에 대해서 알아보겠습니다.

기본 키 맵핑은 내용이 어렵진 않은데 데이터베이스에서 기본 키 맵핑을 어떤 식으로 하는지 알고 계셔야 돼요.

우선 기본 키 맵핑은

이겁니다.

제가 좀 쉽게 하기 위해서 나머지는 이제 다 지워버릴게요.

그래서 기본 키 맵핑은 이겁니다.

그리고 사용할 수 있는 annotation은 크게 두 가지가 있습니다. @Id랑 @GeneratedValue 자 이제 하나씩 설명을 드릴게요.

'내가 Id를 직접 할당할 거야' 그러면 이번에는 Id를 String으로 바꾸겠습니다.

String으로 바꾸고,

타입이 바꼈으니까 Getter, Setter 만들어줄게요.

그 다음에 Id를 예를 들어서 ID_A 라고 저장하고 실행해 보겠습니다.

이렇게 됐고, 데이터베이스를 보면,

id가 ID_A, name은 C 이렇게 들어가있죠.

예를 들어서 뭐 뭔가 막 이것저것 조합하는 거에요. id로 할 수 있는 게 사실 별로 없는데 여러가지 코드성 데이터를 막 조합을 해 가지고 내가 직접 Id를 만들어서 할당한다. 이럴 때는 @Id만 쓰시면 됩니다.
그러니까 @Id만 쓰면 내가 직접 Id를 세팅을 해준다는 의미예요. 그런데 보통 관계형 DB를 쓰시면 이렇게 하는 것보다는, 사실 예를 들어서 오라클 같은 경우엔 Sequence를 쓰죠. 이건 DB가 자동으로 숫자 값을 제너리트 해주는 걸 쓰는 거죠. 그 다음에 MySQL 계열 같은 경우엔 AutoIncrement 그러니까 내가 값을 그냥 Null로 해서 집어넣으면, DB가 알아서 거기에 값을 딱 세팅해줍니다. 그냥 순차적으로 올라가게 DB가 값을 넣어주는 식으로 값을 뭔가 자동으로 할당해주는 방법들이 있습니다. 이거를 @GeneratedValue라는 어노테이션을 사용해서 쓰시면 되고요.

직접 할당은 @Id를 쓰면 된다고 방금 말씀드렸고

이제 자동 할당에 대해서 설명을 드리겠습니다.

그냥 하나씩 보면서 설명을 드릴게요.

우선 첫번째 예시, @GeneratedValue 이고 뭔가 이제 '나는 값을 생성해서 쓰고 싶어, 내가 직접 할당하는 게 아니야' 라고 하고 싶으면,

이렇게 전략이 있습니다. 첫번째 전략 strategy라고 넣으시면 되는데, 기본 값은 AUTO 거든요. AUTO는 db 방언에 맞춰서 자동으로 생성이 됩니다. 예를 들어서 제가 오라클이면 Sequence가 생성되고 이제 뭐 그런 식입니다.

그래서 이건 확인해 보고 쓰셔야 되구요.

그리고 Identity.

세 개밖에 없어요. Identity, 그 다음에 Sequence, Table 이 3가지 밖에 없습니다. Auto는 이 3가지 중에 하나가 선택이 되는 거고요. 데이터베이스 방언에 맞춰서.
제일 먼저 Identity는 뭐냐면,

기본 키 생성을 그냥 데이터베이스에 위임하는 거예요. 이제 단적인 예가 MySQL의 AutoIncrement 입니다. 자 한번 해볼게요. 그냥 실행을 하면 지금은 안되겠죠.

왜냐면 지금은 자동 생성이기 때문에 값을 내가 세팅을 하면 안 되는 거예요.

그래서 이거를 지워주고 다시 돌려보면,

잘 보시면 Insert 쿼리가 나갔죠. 값을 Null로해서 넣으면 DB가 어떻게 되냐면,

자 일단 create 문을 봐야 되는데요. 보시면 create table 해가지고 id varchar(255) 다음에 id 가 generated by default as identity 라고 되어있죠.

이게 뭐냐면 만약에 이걸 mysql 방언으로 바꿔서 실행해 보면,

보시면 auto increment 라고 나오죠.

h2db 에서는 방금처럼 사용하면 되는 거구요.

지금 db에 가보면 id 1이 들어가 있죠. 한 번 더 실행하면 id 2가 들어갈 거에요. 왜냐하면 그냥 내가 만든 다음에 persist를 한 거니까요. 근데 create로 하면 table을 drop을 시키니까 안 되겠죠.

그래서 none으로 바꿔주고 다시 실행시켜볼게요.

db에 가보면 Insert가 되면 두번째로 들어가겠죠.

지금 코드 자체가 그냥 member를 만들고 id 값 세팅 안하고,

그냥 딱 persist 한거죠.

그래서 이 query도 잘 보시면 insert into에 Member에 id name이 있는데 id가 Null로 들어갑니다.

그러니까 identity는 뭐냐면 '나는 모르겠고 db야! 니가 알아서 해줘' 이겁니다. 밑에 쭉 설명이 있는데요, 이거는 뒤에 다른 전략들 설명 드리고 나서 설명 드릴게요.

일단 이렇게 하면 된다고 알아두시면 됩니다.
그 다음 Sequence 전략.

Sequence는 주로 오라클 같은 데이터베이스에서 많이 사용을 하는데요.

이렇게 SEQUENCE라고 쓰고,

일단 create로 바꾸고 실행을 해보겠습니다.

보시면 Create Sequence 라고 해서 Sequence Object를 만들어냅니다. 원래 Sequence가 그런 거잖아요. 데이터베이스에 있는 Sequence Object를 통해서 값을 Generating 하는 거죠. 그 다음에 어떻게 합니까?

보시면 call next value 뭐 이런거 보이시죠. 이 Sequence Object 를 통해서 값을 가져온 다음에 Id 값을 Setting을 거는 겁니다.

자 보면, 지금 값이 안 들어가 있네요.

왜 그러냐면 이게 문자면 안되겠죠?
그럼 숫자면 뭘 쓰면 좋나요? 라고 하실 수 있는데,

int는 일단 좀 애매해요. 왜냐하면 int는 0이기 때문에.

그래서 Integer를 쓰셔야 돼요. 그런데 Integer도 조금 애매해요. 왜냐면 이 Sequence Object가 Integer는 10억 넘어가면 한 바퀴 돌거든요. 그러면 십몇억 이상은 안돼요.

그래서 결론은 Long을 써야 되는데, 왜냐면 여러분 이게 생각보다 Integer랑 Long이랑 숫자를 따지면 공간이야 두 배 정도 차이가 나겠지만, 사실 예를 들어 우리 애플리케이션 전체를 생각할 때는 거의 Integer를 Long으로 바꾸는 것은 정말 옛날처럼 막 성능이 너무 안 나올 때는 모르겠지만 지금은 거의 영향을 주지 않아요. 10억이 넘어갈 때 타입을 바꾸는 게 오히려 더 힘들어요. 그래서 그냥 제가 권장하는 것은 이런 경우에는 그냥 long을 쓰는 걸 권장드립니다.

자 이렇게 하면 또 타입이 바뀌니까 getter, setter 다시 해주고,

실행을 해보면,

db에 id가 1이고 name은 c로 되어있죠.

다시 none으로 설정을 바꾸고 한번 더 실행을 해보면,

call next value 해서 hibernate_sequence에서 값을 가져온 다음에 세팅을 해서

여기 id가 2가 세팅이 되어서 db에 들어가게 됩니다.

해보면 두번째 c 나오죠. 이게 이제 Sequence 전략이구요.

지금 보시면 hibernate_sequence 라는 걸 쓰고 있어요. 이름을 안줬기 때문에 이제 Hibernate가 만드는 기본 Sequence를 쓰는데요. 사실 Table마다 Sequence를 따로 관리하고 싶잖아요.

그러면 이렇게 @SequenceGenerator 라는 걸 가지고 맵핑을 하시면 됩니다.

이렇게 해서 sequenceName을 적어 주시면, 이런 경우에 MEMBER_SEQ 라는 게 생성이 됩니다. 물론 내가 create table로 직접 생성을 해도 되고요. 지금은 이제 Auto DDL 해서 자동으로 해주는 거죠.
자 그리고 내가 원하는 Generator를 사용할 때 MEMBER_SEQ_GENERATOR 라고 맵핑을 걸으면 MEMBER_SEQ로 동작하게 됩니다.

한번 해볼게요. 이렇게 하고,

create로 다시 바꾼 다음에 실행을 하면,

Sequence를 MEMBER_SEQ 라고 만들죠.

자 그 다음에 Sequence 전략도 좀 advanced한 내용이 있는데요. allocationSize랑 initialValue 라는 게 있는데, 이걸 가지고 뭔가 성능 최적화를 해내요.

이거는 좀 이따 마지막에 따로 설명드릴게요.
자 그 다음에 Table 전략.

Table 전략은 뭐냐면 키 생성 전용 Table을 하나 만드는 거예요. 그래서 데이터베이스 Sequence를 흉내내는 전략입니다. 이거 장점은 뭐냐면 어떤 데이터베이스는 AutoIncrement가 있고 어떤 데이터베이스는 Sequence가 있잖아요. 그러니까 두 가지 중에 선택을 해야 되는데, Table 전략은 그냥 모든 DB에 다 적용할 수가 있어요. Table을 하나 딱 만들어서 거기서 그냥 계속 GenerateKey를 뽑는 거예요.
근데 이제 단점은 아무래도 Table을 직접 사용하니까 이게 별도의 Table이라고 해도 아무래도 락도 걸릴 수도 있고 등등 성능에 이슈가 있죠. 아무래도 Sequence Object나 이런 것들은 숫자 뽑는 데 다 최적화가 되어 있는데 이거는 최적화가 안 되어있기 때문에 단점으로는 성능이 좀 떨어진다는 단점이 있습니다.

그래서 기본 키 맵핑은 Table은 사실 거의 Sequence랑 비슷하게 써요.
그냥 한번 보여드리겠습니다.

@TableGenerator 하고 복사 붙여넣기 하겠습니다. 보시면 맵핑 전략을 table 이라고 잡았어요.

그 다음에 generator를 MEMBER_SEQ_GENERATOR, 그러니까 위에 만든 애로 잡았구요.

그 다음에 여기 보시면 @TableGenerator 라는 걸 했구요.

MY_SEQUENCES 라고 Table 이름을 적어주고,

Pk 컬럼명을 MEMBER_SEQ 라고 적어주고 실행하면,

Create Table 해서 MY_SEQUENCES 라는 게 생성이 되구요.

보시면 next_val, 그 다음에 sequence_name 뭐 이런 컬럼들이 생성이 되구요. 이걸 이제 DB에 데이터가 어떻게 들어가는지 보면 됩니다.

refresh 이거 눌러주시면 됩니다.

보시면 Sequence Name은 MEMBER_SEQ 라고 제가 지정해 준 거구요. 현재 Next Value는 1. 이게 계속 넣으면 이 Value가 점점 증가하겠죠.

그리고 이 ID 1번이 방금 거기서 따온 거라고 보시면 됩니다.

자 이게 이제 Table 전략이구요. 근데 운영에서는 아무래도 Table 전략을 쓰기 조금 부담스럽죠. 왜냐면은 DB에서 보통 관례로 쓰는 것들이 있기 때문에 그냥 걔를 쓰시는 걸 저는 권장드려요.

여기에도 보면 여러가지가 있는데요. 속성들을 그냥 간단하게만 소개 드리겠습니다. 사실 Table 맵핑 전략을 잘 쓰진 않아서 그냥 뭐 이런 것들이에요.

이러한 컬럼명을 바꿀 수 있다는 거예요.

중요한 거는 initialValue, allocationSize인데요. 사실 Sequence 전략에서의 어떤 성능 최적화랑 똑같은 내용이어서 마지막에 설명 드리겠습니다.
자 그러면은 권장하는 식별자 전략은 뭐냐? 식별자 전략이 이렇게 많잖아요.

일단 이거는 DB를 먼저 생각을 해야 되는데, 데이터베이스의 기본 키 제약 조건에 대해서 그냥 먼저 생각 자체를 해보는 게 필요해요.
기본 키 제약 조건은 null이면 안 되죠. 값이 있어야 되죠. 그 다음에 유일해야 되죠. 그 다음에 변하면 안 되죠. 이 세 가지를 만족해야 이제 데이터베이스 PK 조건으로 쓸 수가 있거든요.
그런데 여기서 사실 앞에 두 개는 쉬운데 변하면 안 된다는 게 정말 어려워요. 왜냐하면 이게 정말 먼 미래까지 변하면 안 되거든요. 수명이 어떻게 될 지 모르지만 몇십년동안 바뀌면 안 된다는 거에요.
그런데 문제는 뭐냐면 미래까지 변하지 않는다는 이 조건을 만족하는 자연 키, NaturalKey 라고 하는데 자연 키를 찾기는 어려워요.
자연 키는 뭐냐면 비즈니스적으로 의미있는 키, 주민등록 번호, 전화 번호 이런 것들을 자연키라고 해요. 대신에 이 자연키를 찾기는 어려워요.
그래서 대신에 대리키, 대체 키가 번역이 애매한데 아까 말씀드린 GenerateValue나 랜덤 값이라던가, 비즈니스와 전혀 상관없는 키를 쓰시는 걸 권장을 드려요.
왜냐하면 예를 들어서 주민등록 번호도 기본 키로 적절하지가 않아요. 주민등록 번호를 기본 키로 썼다가 나라에서 막았던 사례도 있듯이 엄청 난리가 났었거든요.

그래서 사실 제가 권장하는 방법은 타입은 Long 형으로. 왜냐하면 10억이 넘어도 동작해야 되니까. 그 다음에 이제 대체 키. 뭐 Sequence를 쓴다거나, 아니면 UUID를 쓴다거나, 이런 대체 키를 쓰시고 이런 키 생성 전략들은 조합해서 쓰시는 걸 저는 권장을 드려요. 결론적으로 말씀드리면 그냥 AutoIncrement나 아니면 Sequence object 둘 중에 하나를 쓰시고요.
그게 아니면 때에 따라서는 UUID 같은 거나 아니면 뭔가 랜덤 값을 조합한 회사 내에 어떤 그런 룰들이 있을 수 있어요. 그런 것들을 쓰시는 걸 권장 드려요. 절대 비즈니스를 키로 끌고 오는 것은 저는 권장하지 않아요.
자 그러면 여기까지 해서 이제 엔티티 맵핑에 대해서는 사실 다 알아봤습니다. 그러면 제가 이제 좀 어려운 내용을 설명드린다고 했었죠. 거기로 올라가 보겠습니다.

우선 이 Identity 전략에서 약간 애매한 게 하나가 있어요. 뭐냐면 이 Identity 전략은 데이터베이스에 Insert를 해봐야,

일단 보시면은 이거를 create로 바꾸고, 다 지우고 나서 전략을 IDENTITY로 바꿀게요. 이 IDENTITY 전략은

내가 이 Id에 값을 넣으면 안 돼요. Id를 아무것도 건들이지 않고 DB에 Insert를 해야 된단 말이에요.

그래서 Id가 null로 Insert 쿼리가 날라오면, DB에서 그 때 값을 딱 세팅을 해줍니다.
자 이렇게 하는 게 뭐가 문제냐면, 그럼 Id 값을 알 수 있는 시점이 언제냐면 결국 DB에 값이 들어가 봐야 Id 값을 알 수가 있어요.
그런데 여러분 JPA 생각해보시면, 영속성 컨텍스트 예전에 설명드린 거 있죠. 영속성 컨텍스트에서 관리되려면 무조건 PK값이 있어야 돼요. 근데 얘는 지금 PK값을 당장은 모르고 DB에 들어가 봐야 안단 말이에요. 예전 pt로 쭉 가서 보면,

영속 상태가 됐다는 건 영속성 컨텍스트 안에 1차 캐시가 있다고 설명드렸죠? 여기에 @Id가 DB의 PK 값이란 말이에요.
근데 Identity는 DB에 넣기 전까지는 이 @Id 값을 모른단 말이에요. 원래 영속성 컨텍스트 안에서 Insert 쿼리를 모으고 있다가 한번에 DB에 날린다고 했잖아요. 그래서 원래는 DB에 값이 들어가기 전에 1차 캐시 안에다가 id를 미리 적어놓고 있는 건데, 여기서는 id 값을 미리 알 수가 없으니 적어놓을 수가 없는 거죠.
그러니까 JPA 입장에선 지금 키가 없으니까 값을 넣을 수 있는 방법이 없어요. 그래서 약간은 울먹겨자먹기로 어떻게 하냐면,

이 Identity 전략에서만 딱 예외적으로 db에 em.persist를 호출하자마자 데이터베이스에다가 Insert 쿼리를 날려버립니다.

em.persist 로 호출하는 시점에 바로 Insert 쿼리를 날리는 거죠. 제가 원래는 commit 하는 시점에 Insert 쿼리가 날아간다고 했잖아요. 그런데 Identity는 DB에 값을 Insert 쿼리로 날려봐야 PK값을 그때서야 알 수 있기 때문에 Identity는 일단 이 시점에 바로 Insert 쿼리가 날아가 버립니다.

확인하기 쉽게 이렇게 해놓을게요. 그리고 실행을 해보면,

보시면 이 사이에 persist를 호출하는 시점에 실제 DB에다가 Insert 쿼리를 날려버립니다.

보시면 여기 id에 null을 넣어둔거 보이시죠.

지금 데이터베이스에 가보면, 값이 1 이라고 되어있죠. 그리고 JPA가 내부적으로 1 값을 select 해서 가지고 옵니다. 그래서 이 영속성 컨텍스트에 Member의 Id 값을 1이라고 해서 세팅이 들어가죠.

em.persist 하는 시점에 이 id 값을 알 수가 있어요.

그럼 member.id를 출력해볼게요. 진짜로 db에 insert 쿼리가 날라가서 저장된 걸 가지고 오는지 보겠습니다.

보시면 id = 1 나오죠. 그니까 em.persist 하는 시점에 일단 DB에 집어넣고, DB가 1을 생성하잖아요? 그 Generate Value, Auto Increment가 뜨면서 DB가 생성한 값을 select 해서 읽어옵니다.
그럼 '어? 그럼 Select 쿼리가 나가야 되는 거 아니에요?' 라고 하실 수 있는데 그 JDBC 드라이버에 그런 게 있어요. 값을 딱 넣고 Insert 쿼리를 했을 때 바로 리턴을 받는 게 내부적으로 다 짜져 있습니다. 그래서 DB에 Insert하는 시점에 id 값을 바로 알 수 있어요. 그래서 그걸 JPA가 내부적으로 들고 와서 여기에 바로 세팅을 걸고, 영속성 컨텍스트의 PK 값으로 쓰게 됩니다.

그래서 원래처럼 모아서 Insert하는 게 Identity 전략에서는 불가능합니다. 그게 단점이라고 보시면 됩니다.
근데 사실 저도 이것저것 많이 개발해봤는데 버퍼링해서 write하는 게 크게 메리트가 있지는 않아요. 한 트랜잭션 안에서 Insert 쿼리가 나가게 되면, 예를 들어서 트랜잭션을 계속 잘라내면 성능에 문제가 큰데 한 트랜잭션 안에서 Insert 쿼리가 여러 번 네트워크 탄다고 해서 그렇게 막 비약적으로 성능에 차이가 나지는 않더라고요.

아무튼 이 identity 전략은 데이터베이스 Insert 쿼리를 실행한 이후에야 id 값을 알 수가 있습니다.
그래서 제가 전략을 딱 바꾸면, Sequence나 아니면 직접 id 값을 세팅하거나 하면, 실제로는 tx.commit() 하는 시점에 쿼리가 날라갑니다. 이미 id 값을 알고 있기 때문에 Identity 전략과 이런 차이가 있구요.

그 다음에 Sequence 전략의 특징을 보면, 이것도 빠르게 복붙할게요.

실행을 해보면,

우선 제가 MEMBER_SEQ 라는 Sequence Object를 만들어냈어요.

처음부터 보면, create Sequence 해서 MEMBER_SEQ 라는 걸 만듭니다.

근데 보시면 Start with 1 Increment by 1 이라고 되어 있어요. 이건 뭐냐면 ;1부터 시작하고 1씩 증가시켜 라는 겁니다. Sequence Object가 보통 1부터 시작하고 하나씩 증가시켜 라고 세팅을 하기 때문에 크게 이 옵션에 대해서 모르셨을 수도 있는데 이런 옵션이 있습니다. 그리고 이제 jpa는 어떻게 동작하냐면,

Member 객체를 생성을 하고 그 다음에 id 값은 당연히 세팅을 하면 안 되죠. 왜냐면 지금 generator value에서 Sequence 전략.

이 generator에 맵핑된 MEMBER_SEQ 에서 내가 값을 얻어오고 싶단 말이에요. 그런데 얘도 DB에 가봐야 알 수 있죠. 왜냐면 Sequence Object은 DB가 권리하는 거니까.

그래서 여기 보시면 MEMBER_SEQ가 있고, 현재 값 1이라고 나와있네요. 그래서 여기서 이 값을 애플리케이션으로 가져와야 돼요.

이제 Member 객체를 만든 다음 em.persist 를 할 때 영속성 컨텍스트에 일단 넣어야 되잖아요. 영속성 컨텍스트에 넣으려면 항상 PK가 있어야 되거든요. 그러면 먼저 sequence를 가져와야 된단 말이에요. 그 sequence에서 내 PK 가져와야 돼요.

자 그러면 '어? 너 Sequence 전략이네? 그러면 내가 MEMBER_SEQ_GENERATOR에서 값을 먼저 가져올게!' 라고 JPA가 그렇게 메커니즘 동작합니다.

그래서 이 MEMBER_SEQ에서 실행이 됩니다.

자 이 사이에 있는 call next value for MEMBER_SEQ 라고 해서 쿼리가 나가는 게 보이시죠? '데이터베이스에 있는 MEMBER_SEQ 한테 다음 값 내놔!' 라고 한거에요. 그게 현재 값 1이고요. 다음에 호출하면 2가 될 거에요. 그래서 이 1 값을 얻어 가지고

em.persist 할 때 영속성 컨텍스트 넣으려고 하는데 보니까 어? Sequence 전략이네? DB한테 값을 얻어 와야 되겠네! 하고 DB에서 값을 얻어 와서

이 Member에 이 Id의 값을 딱 넣어줍니다. 그 다음에 영속성 컨텍스트 딱 저장하는 거예요. 그러면 아직 DB에 Insert 쿼리는 안 날아가죠. 왜냐면 PK 값만 딱 얻고 필요하면 버퍼링 이런 것들을 해야 되니까. 그냥 영속성 컨텍스트에 딱 쌓여 있고 실제 트랜잭션을 커밋하는 시점에

여기 보시면 이 Insert 쿼리가 호출이 되게 됩니다. 이제 Sequence 방식은 버퍼링 하는 게 가능한 거예요. 쭉 모았다가 한 번에 탁 write 하거나 이런 게 다 가능합니다. Identity는 어차피 Insert 쿼리를 날려야 알 수 있으니까 버퍼링이 안됐쬬. 근데 Sequence 전략은 이제 db에 있는 Sequence만 살짝 얻어 온 거죠.
그런데 사실 지금 이 얘기를 듣다보면 여러분 이제 그 고민이 드실 거에요. 성능에 대해서 고민하시는 분들은 아 그러면 자꾸 이거 네트워크로 왔다갔다 해야 되지 않냐, 그냥 차라리 Insert 쿼리 한번 날리지. 너무 복잡하고 성능상 또 좋지 않다. 라고 충분히 하실 수 있어요.

그래서 JPA에 뭐가 있냐면 allocationSize가 있습니다. 그러면 이 allocationSize를 가지고 성능을 어떻게 증가하는지 한번 보여드리겠습니다.

아까는 제가 allocationSize를 1로 해놨었는데,

보시면 기본값이 50으로 되어있단 말이에요. '왜 50으로 되어있지?'

제가 이제 좀 코드를 준비를 해놨어요. A, B, C 가 있는데 이거를 저장을 할 때마다 자꾸 next call로 db에서 가져오면 어쨌든 네트워크를 타기 때문에 이제 성능 문제가 좀 생길 수 있겠죠. 그래서 이거를 최적화 하려고 이 방법을 쓰는 건데요.

쉽게 얘기하면 미리 50개를 땡기는 거에요. next call 한번 할 때 DB에는 딱 50개를 미리 올려놓고 제가 메모리 상에 예를 들면 DB는 51번으로 딱 세팅을 해놓는 거에요. 한번 호출할 때 50개씩 늘거든요. 이렇게 해놓고 나는 메모리에서 1씩 쓰는 거에요. 그러다가 어? 딱 50개 담았네? 그러면 또 next call을 한 번 더 호출해 줍니다. 그러면 db에는 50에서 100번대로 가 있겠죠. 그럼 나는 다시 51번부터 쭉 쓰는 겁니다.
db에 먼저 미리 올려놓고 메모리에서 그 개수만큼 쓰는 방식입니다. 이게 아주 기가 막혀요. 여러 웹 서버가 있어도 동시성 이슈 없이 다양한 문제들이 해결됩니다.

먼저 제가 아무것도 없이 한번 실행해 볼게요. DB에 저장 안하고 그냥 create만 된 거 보려고요.

보시면 Create Sequence 해서 Start with 1 Increment by 50 이렇게 하면 1부터 시작하고 50개씩 늘리겠다는 거에요.

db 한번 보면, 자 이게 현재 값이 -49고 50이에요. 왜 이렇게 되어있냐면 보통 call next value 해서 한번 호출해서 나온 값을 쓰는데, 한번 호출했을 때 처음에 1이 되기를 기대하는 거에요.

그래서 실행해 보면 1 값을 세팅하고 이렇게 동작하거든요.

Member 2번 3번 저장을 안 하고, 이 사이를 다시 돌려보시면,

call next value for MEMBER_SEQ가 두 번 호출이 됩니다. '왜 2번 되지?' 지금 저희가 성능 최적화를 하려고 했죠.

처음 호출하면 DB의 SEQUENCE 값은 1이 되고 두 번째 호출하면 DB의 SEQUENCE 값은 51이 됩니다.
무슨 말이냐면 처음에는 나는 50개씩 메모리에 써야 되는데 처음 호출해봤더니 1인 거예요. '어? 지금 뭔가 문제가 있나보네' 하고 한 번 더 호출한 겁니다. 그러면 51이란 말이에요. 그럼 그 다음부터는 지금 현재 내 Sequence는 뭐죠?

지금 처음 호출했을 때 애플리케이션은 1을 쓸 거란 말이에요. 그리고 그 다음부터는 애플리케이션은 2를 쓸 거란 말이에요.

자 여기서 이제 DB SEQ가 51로 되어 있는데, 애플리케이션이 한 번 더 저장하면 나는 3으로 쓸 거에요. 두 번째서부터 이제 똑같죠.

자 이렇게 하게 되면 처음 시점에 최적화 51개로 맞추고요. 한 번은 그냥 dummy를 호출한 거고 51개로 맞춘 그 다음부터는 DB를 직접 호출하지 않고 메모리에서 호출이 됩니다.
한번 실행해 보겠습니다.

보시면 1, 2, 3이 나와있고 next call은 아까 1 맞춘 거고, 여기는 51번까지 미리 확보한 거고요. 그래서 앞으로 4번, 5번 쭉 돌리면 이때부턴 더 이상 next call이 호출되지 않습니다.
대신에 51번을 딱 만나는 순간 미리 50개를 확보해야 되기 때문에 next call이 한번 더 호출이 됩니다.

지금 db를 보면 Sequence가 현재 값이 51번이죠. 51번이 되고 나면 그 다음 next call이 일어난다고 보시면 됩니다.

그래서 이론적으로는 이 값을 많이 증가시키면 더 좋지 않나? 하는데, 웹서버를 내리는 시점에 날라가는 거죠. 그 사이에 이제 숫자 구멍이 생기는 거예요. 사실 구멍이 생겨도 상관은 없는데 그게 괜히 낭비가 되는 거죠. 그래서 한 50정도나 100정도가 적정한 것 같아요.

그 다음에 이제 Table 전략도 마찬가지입니다.

Table 전략에도 initialValue와 allocationSize가 있는데요. allocationSize가 방금 말씀드린 것과 똑같은 전략입니다. Table에 Sequence 값을 Increment를 한 50개를 미리 해놓고 웹서버에서 50까지 쭉 쓰는 거예요.
그러면 이제 막 고민이 되실거에요. '서버가 여러 대면 문제가 없나?' 막 고민되실 수 있는데 사실 문제가 없습니다.
아까 Sequence 전략도 그렇고 미리 값을 올려두는 방식이기 때문에 여러 대가 동시에 붙더라도, 동시에 호출하면 이제 쭉 값이 올라가있겠죠. 50개 동시 호출을 하더라도 이게 자기가 숫자를 미리 확보를 하기 때문에 크게 문제가 없습니다.
특히 Sequence 같은 경우에는 next call 호출을 하고 자기가 그 값을 받잖아요. 그러니까 51이면 '아 내가 1부터 50개까지 쓸 수 있겠구나' 어떤 애는 그 다음 호출 되니까 100이라고 받으면 '아 난 50부터 100까지 쓸 수 있구나' 이렇게 되기 때문에 동시성 문제는 없습니다.

여기까지 하고 조금 있다가 실전 예제 한번 알아보겠습니다.
'스프링 > 자바 ORM 표준 JPA 프로그래밍 - 기본편' 카테고리의 다른 글
| 단방향 연관관계 (0) | 2024.06.10 |
|---|---|
| 실전 예제 1 - 요구사항 분석과 기본 매핑 (0) | 2024.06.08 |
| 데이터베이스 스키마 자동 생성 (0) | 2024.05.27 |
| 객체와 테이블 매핑 (0) | 2024.05.27 |
| 정리 (0) | 2024.05.26 |