[JPA] [4] 엔티티 매핑(하편) - 기본키 매핑
https://www.inflearn.com/course/ORM-JPA-Basic
인프런 김영한 님의 강의를 듣고 작성한 글입니다.
[4] 기본 키 매핑
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id
1. 직접 매핑 : @Id만 사용
2. 자동 생성(@GeneratedValue)
- IDENTITY: 데이터베이스에 위임, MYSQL
- SEQUENCE: 데이터베이스 시퀀스 오브젝트 사용, ORACLE
@SequenceGenerator 필요 - TABLE: 키 생성용 테이블 사용, 모든 DB에서 사용
@TableGenerator 필요 - AUTO: 방언에 따라 자동 지정, 기본값
■ 자동 생성 네 가지 전략
① IDENTITY
@GeneratedValue(strategy = GenerationType.IDENTITY)
기본키 생성을 데이터베이스에 위임한다.
예를 들어 id값을 null값으로 넣으면 DB가 알아서 AUTO_INCREMENT 처리를 해준다.
로그 화면
▶ 특징
1] IDENTITY 전략은 entityManager.persist() 시점에 즉시 INSERT SQL을 실행하고 DB에서 식별자를 조회한다.
이유 : JPA 영속성 컨텍스트는 pk값이 필요하다. 하지만, AUTO_INCREMENT는 DB에 INSERT SQL을 실행한 이후에야 id값을 알 수 있다. 따라서 IDENTITY를 쓰면 트랜잭션 commit 시점이 아닌 persist()시점에 INSERT SQL을 실행한다.
JpaMain & 로그화면
▶ 단점 : 모아서 insert 쿼리문을 날릴 수 없다. 그러나 버퍼링해서 write한다고 성능에 큰 이득이 있지 않기 때문에 크게 신경 쓸 필요는 없다.
② SEQUENCE
@GeneratedValue(strategy = GenerationType.SEQUNCE)
데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트다. (예: 오라클 시퀀스)
오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용된다.
Member
@Entity
@SequenceGenerator(
name = "MEMBER_SEQ_GENERATOR",
sequenceName = "MEMBER_SEQ", // 매핑할 데이터베이스 시퀀스 이름
initialValue = 1,
allocationSize = 1)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
}
로그화면
▶ 특징
1] SEQUENCE 전략은 generator에 매핑된 MEMBER_SEQ에서 id값을 얻어온다.
2] SEQUENCE 전략도 persist()를 호출하기 전에 pk값이 필요하다. 그래서 SEQUENCE는 미리 DB SEQUENCE에서 id값을 가져온다.
▶ 성능향상 문제
SEQUENCE 전략을 사용하면 키 값 세팅을 위해 매번 쿼리를 날려야 한다. 이러면 성능상 문제가 있을 수밖에 없다. 그래서 JPA에서는 allocationSize 세팅을 통해 미리 시퀀스 값을 50개씩(디폴트값) 가져오는 식으로 쿼리를 날리는 횟수를 줄인다.
예시)
//DB SEQ = 1 | 1
//DB SEQ = 51 | 2
//DB SEQ = 51 | 3
em.persist(member1); //1, 51
em.persist(member2); // MEM
em.persist(member3); // MEM
시퀀스 초기값을 -49가 아닌 1로 맞추기 위해 dummy를 호출한 뒤 최적화를 위한 호출을 한다.
▶ @SequenceGenerator
③ TABLE
@GeneratedValue(strategy = GenerationType.TABLE)
키 생성 전용 테이블을 만들어 데이터베이스 시퀀스를 흉내 내는 전략이다.
Member
@Entity
@TableGenerator(
name = "MEMBER_SEQ_GENERATOR", //테이블 제네레이터 이름
table = "MY_SEQUENCES", //테이블 이름
pkColumnValue = "MEMBER_SEQ", allocationSize = 1)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
}
▶ 특징
장점 : 모든 데이터베이스에 적용가능하다.
시퀀스(대표 : 오라클)와 AUTOINCREMENT(대표 : MySQL)는 쓸 수 있는 DB가 한정돼 있다. 반면 테이블 전략은 테이블을 만들어서 키를 가져오는 것이므로 어떤 DB에서도 사용할 수 있다.
단점 : 성능저하
시퀀스와 달리 테이블은 숫자를 뽑는 데 최적화 돼 있지 않다. 아울러 테이블을 직접 사용하니 테이블이 Lock이 걸릴 우려가 있다.
▶ @TableGenerator - 속성
SEQUENCE 전략처럼 allocationSize로 최적화할 수 있다.
④ AUTO
@GeneratedValue(strategy = GenerationType.AUTO)
방언에 따라 IDENTITY, SEQUENCE, TABLE 전략을 자동으로 지정한다.
■ 권장하는 식별자 전략
- 기본 키 제약 조건: null이 아니고 유일해야 하며 변하면 안 된다.
- 미래까지 이 조건을 만족하는 자연키는 찾기 어렵다. 대리키(대체키)를 사용하자.
- 자연키 : 비즈니스적으로 의미 있는 키(예: 주민등록번호, 전화번호 등)
- 대체키 : 비즈니스와 관계없는 키(예: 랜덤값)
예를 들어 주민등록번호도 기본 키로 적절하지 않다. 개인 정보 보호를 목적으로 정부에서 주민번호를 저장하지 말라는 돌발상황이 발생할 수 있기 때문이다. 이러면 해당 테이블뿐만 아니라 해당 테이블과 JOIN된 다수의 테이블에서 문제가 발생한다.
• 권장: Long형 + 대체키 + 키 생성전략 사용
[5] 실전 예제 1 - 요구사항 분석과 기본 매핑
■ 요구사항
- 회원은 상품을 주문할 수 있다.
- 주문 시 여러 종류의 상품을 선택할 수 있다.
■ 기능목록
- 회원 기능
- 회원등록
- 회원조회
- 상품 기능
- 상품등록
- 상품수정
- 상품조회
- 주문 기능
- 상품주문
- 주문내역조회
- 주문취소
■ 도메인 모델 분석
■ 테이블 설계
■ 엔티티 설계와 매핑
item & Member & OrderItem & Order
■ 데이터 중심 설계 문제점
- 현재 방식은 객체 설계를 테이블 설계에 맞춘 방식
- 테이블의 외래키를 객체에 그대로 가져옴
- 객체 그래프 탐색이 불가능
- 참조가 없으므로 UML도 잘못됨
Order & JpaMain
이 문제를 해결하기 위해 연관관계 매핑이 필요하다.