백엔드/JPA

[JPA] [4] 엔티티 매핑(하편) - 기본키 매핑

RE-Heat 2023. 8. 6. 23:59

https://www.inflearn.com/course/ORM-JPA-Basic

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

초급자를 위해 준비한 [웹 개발, 백엔드] 강의입니다. JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자

www.inflearn.com

인프런 김영한 님의 강의를 듣고 작성한 글입니다.

 

[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 처리를 해준다.

 

로그 화면

H2(좌)과 mySQL(우)

 

▶ 특징

1] IDENTITY 전략은 entityManager.persist() 시점에 즉시 INSERT SQL을 실행하고 DB에서 식별자를 조회한다.

이유 : JPA 영속성 컨텍스트는 pk값이 필요하다. 하지만, AUTO_INCREMENT는 DB에 INSERT SQL을 실행한 이후에야 id값을 알 수 있다. 따라서 IDENTITY를 쓰면 트랜잭션 commit 시점이 아닌 persist()시점에  INSERT SQL을 실행한다.

 

JpaMain & 로그화면

persist 시점에 insert문이 날아가는 것을 확인할 수 있다.

 

▶ 단점 : 모아서 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값을 가져온다.

로그화면을 보면 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를 호출한 뒤 최적화를 위한 호출을 한다.

 

3개를 불렀는데 총 2번만 호출하는 것을 확인할 수 있다. 이후 50이 넘으면 자동으로 시퀀스가 50개를 더 불러온다.

 

▶ @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

 

이 문제를 해결하기 위해 연관관계 매핑이 필요하다.