🪄 기본키 전략
기본키는 유일한 값으로 개발자가 지정해줘도 되지만, 실무에서 보통 자동생성 방식을 따른다
프로젝트 이력을 돌아보면, 나는 보통 기본키 자동 생성 전략으로 IDENTITY를 사용해왔다.
이번 프로젝트를 개발하면서 IDENTITY 사용시 field 'xxx_id' doesn't have a default value가 뜨는 것을 보고, 데이터베이스와 JPA를 다시 짚어봐야겠다는 생각이 들었다.
🪄 기본키 자동생성 전략 4가지
이 기본키 자동생성 전략은 본질적으로 insert쿼리가 언제 나가느냐의 차이다.
✨ AUTO
아래 IDENTITY, TABLE, SEQUENCE 중 자동선택한다.
✨ IDENTITY
설명
- 기본 키 생성을 데이터베이스에 위임한다.
- insert 쿼리를 확인해보면 id에 null이 들어가지만 그 id를 DB에서 자동생성해준다.
- insert시 id를 1씩 증가시키며 레코드를 생성한다.
insert 시점
em.persist()로 객체를 영속화시키는 시점에 (save()를 호출하는 시점에) 곧바로 insert 쿼리가 DB로 전송되며 id가 생성된다. 이 부분이 특별한 이유는, 원래는 객체를 save()로 저장하더라도 flush 시점에(트랜잭션이 끝나는 commit 이전 시점) insert 쿼리가 나간다. 그런데 이 경우에는 DB에 의해 자동생성된 id값이 필요해서 미리 flush가 이루어지며 insert쿼리가 나간다. DB가 부여한 이 id를 반환받아 1차캐시에 엔티티를 등록시켜 관리한다(1차캐시에 id값을 키로 들고있어야 관리가 됨) 따라서 IDENTITY 에서는 반드시 insert 쿼리를 먼저 날려서 데이터베이스가 id생성을 먼저 수행하게 된다.
✨ SEQUENCE
설명
- DB Sequence Object를 사용하여 id를 증가시킨다.
- DB Sequence란 유일값인 id를 순서대로 생성하는 특별한 오브젝트다. Sequence Name을 지정하여 테이블마다 Sequence Object를 따로 관리할 수 있다.
insert 시점
SEQUENCE 전략은 단순히 seq값만 시퀀스 객체에서 조회해오면 id를 알 수 있다. 그래서 바로 위의 IDENTITY 전략과는 달리 엔티티를 insert하여 id를 받아오지 않아도 된다. 객체를 save()로 저장할 때부터 id값을 부여해 저장해야 하므로 seq값 조회가 필요하여 select문이 실행된다. 실제 insert문은 트랜잭션이 끝나는 커밋시점에 실행된다.
특이사항
@SequenceGenerator()를 커스텀하여 트랜잭션별 allocaionSize를 지정할 수 있다. 이를 통해 미리 키본키를 할당받는 단위를 설정하고 기본키를 50개(기본값) 미리 생성받는다. 이 50개는 다른 트랜잭션이 접근할 수 없게 된다. 이때 다른 트랜잭션은 그 다음의 id 50개를 할당받아 사용한다. 이러한 방식으로 트랜잭션간 동시성 이슈를 해결 가능하다.
// 예시 (출처 https://lordofkangs.tistory.com/358)
@SequnceGenerator(
name="MEMBER_SEQ_GENERATOR",
sequenceName = "MEMBER_SEQ",
initialValue = 5, allocationSize = 100
)
MySQL은 지원하지 않는다.
Oracle, PostgreSQL, DB2, H2에서 사용가능하다.
✨ TABLE
설명
- 키 생성 전용 테이블을 하나 만들어서, 데이터베이스 시퀀스를 흉내내는 전략이다.
- 모든 데이터베이스에서 이 기능을 지원한다.
- 성능이 좋지 않아 잘 사용하지 않는다.
insert 시점
SEQUENCE 전략처럼, 시퀀스 '테이블'에서 id값을 얻어와야 해서 select문이 나간다. 이후에 SEQUECNE 전략과 다른 것은, next_val을 증가시키기 위해 시퀀스 '테이블'에 update 쿼리가 나가야 한다는 것이다. insert 쿼리는 트랜잭션 끝나는 커밋시점이다.
🪄문제 상황 파악하기
문제상황
- 데이터베이스: 로컬 H2 / 운영서버에서는 MySQL
- 기본키 생성 전략: IDENTITY
- 문제: H2에서는 오류가 안 터졌는데, MySQL에서는 아래 오류가 터졌다.
field 'mark_id' doesn't have a default value
문제 파악
가장 표면적으로는, 컬럼이 not null인데 null을 넣으려고 하는 상황에서 이 컬럼의 default value가 지정되어있지 않아 일어나는 오류다. 그런데 나는 pk에서 문제가 되어서, not null이어야 하고, insert 쿼리를 날릴 때 null이어도 상관 없어야 한다. 그러면 auto increment가 적용되지 않는 것이 문제이다.
이유 (파악중)
(분명 IDENTITY 전략 상황에서 오류가 떴었는데... 로컬에서 다시하니까 된다. 그래서 운영 서버에서 안 되는 상황 재확인하고 싶은데 공모전 심사기간이라 건드리는 게 위험하다. 심사기간 끝나면 테스트해보겠다)
운영 서버에서는 아직 확인해보지 못했지만,
로컬에서 돌려보니 정상 동작하더라고..! 확인해보니까 auto increment 설정이 적용된 올바른 상황이었다.
나중에 문제가 일어난 서버에서 다시 확인해봐야겠다. IDENTITY 지정시 auto_increment가 create table문에서 붙어있는지 안붙어있는지 먼저 검토해보아야겠다.
만약 또 같은 문제가 발생하는 것을 확인했다면 의문점 체크 필요!
[의문1] 그런데 IDENTITY 전략에서는 기본적으로 create table할 때 auto increment가 지정되는게 자연스러운 현상 아닌가? 그때 왜 문제가 생겼지? 분명 jpa hibernate ddl-auto 설정도 create로 지정하고 jar 빌드했었는데 말이다.
[의문2] 만약 create table문의 차이가 문제점이었다면, 왜 동일한 docker 환경에서 이런 문제가 생기는지는 아직 모르겠다. 혹시 mysql이 Debian 이랑 Window에 따라서 create table 설정에 문제가 있는지..? (테이블 생성에서 그럴 이유는 없지 않나 싶긴 하지만..)
AUTO INCREMENT 확인방법
[방법 1] craete문에서 설정 (확인)
// 다른 컬럼은 생략하고 문제의 mark_id만 남김
create table mark (
mark_id bigint not null auto_increment,
primary key (mark_id)
) engine=InnoDB
[방법 2] workbench에서 AI를 체크
🪄참고: MySQL에서 AUTO일 경우 TABLE 전략
AUTO로 적용했을 때 해결된 이유는, MySQL에서 AUTO일 경우 TABLE 전략을 채택하기 때문이다.
그래서 auto increment 값 부여 관련해서 문제가 일어날 여지가 없었다.
>> 하이버네이트 5부터는 MySQL에서 TABLE을 기본 시퀀스 전략으로 가져간다. 참고로 스프링부트에서는 하이버네이트의 id 생성전략을 그대로 따라갈지 말지 결정하는 useNewIdGeneratorMappings 설정이 있고, 스프링부트 2.0부터는 true값이라 하이버네이트의 id 생성전략을 따라간다.
좋은 글: https://jojoldu.tistory.com/295
'Database' 카테고리의 다른 글
MSA에서 분산트랜잭션 (0) | 2024.05.22 |
---|---|
[상속관계 매핑] 싱글 테이블 전략을 선택한 이유 (0) | 2024.05.16 |
컬럼 수 적절성에 대한 고민 및 테이블 분할 (0) | 2024.04.15 |
Docker Mysql Container의 sql 파일 꾸준히 백업: crontab 사용 (0) | 2024.01.04 |
[DB] 인덱스 사용 이유 이해하기 (0) | 2023.10.17 |