JPA에서 Entity의 기본 키 매핑 방법에 대해 알아봅시다.
JPA에서는 5가지 기본키 매핑 방법이 있습니다. 하나씩 알아봅시다.
자동 생성 전략이 다양한 이유는 DB마다 지원하는 방법이 다르기 때문입니다.
(오라클: 시퀀스 제공, MYsql 시퀀스 제공x, 대신 Mysql AUTO_INCREMENT 사용)
GenrationType.IDENTITY
데이터베이스에 위임
GenrationType.SEQUENCE
데이터베이스 시퀀스 오브젝트 사용, @SequenceGenerator 필요
GenrationType.TABLE
키 생성용 테이블 사용, 모든 TB에서 사용, @TableGenerator 필요
GenrationType.AUTO
방언에 따라 IDENTITY, SEQUENCE, TABLE 중 한개자동 지정, 기본값
키를 애플리케이션에서 직접할당하는 방식입니다.
JPA에서 영속성 컨텍스트에서 엔티티는 키로 구분됩니다.
직접할당하다가 휴먼 에러가 발생할 확률이 큽니다.
package hellojpa;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class User {
// 키를 직접 할당합니다.
@Id
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();;
try {
// 비영속 상태
User user = new User();
// 키를 직접 할당해줍니다.
user.setId(1L);
user.setName("user1");
// 영속성 컨텍스트에 등록
em.persist(user);
// 커밋
tx.commit();
} catch (Exception e) {
// 실패하면 롤백
tx.rollback();
} finally {
// enity manager가 데이터 커넥션을 물고있다. 꼭 닫아준다.
em.close();
}
emf.close();
}
}
기본 키 생성을 데이터베이스에 위임
주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용
(예: MySQL의 AUTO_ INCREMENT)
IENTITY전략은 DB에 저장 후 식별값 확인
JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행
IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행 하고 DB에서 식별자를 조회
왜냐하면 영속성은 엔티티의 식별값을 기준으로 엔티티를 구별하는데 키값이 필요하기 때문입니다.
IDENTIY 전략은 쓰기 지연이 동작하지 않습니다.
ibernate:
/* insert hellojpa.User
*/ insert
into
User
(id, name)
values
(null, ?)
=================
package hellojpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User {
// 기본 키 생성을 데이터베이스에 위임
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();;
try {
// 비영속 상태
User user = new User();
user.setName("user1");
// 영속성 컨텍스트에 등록
// 커밋 전에 insert 쿼리 수행
em.persist(user);
System.out.println("=================");
// 커밋
tx.commit();
} catch (Exception e) {
// 실패하면 롤백
tx.rollback();
} finally {
// enity manager가 데이터 커넥션을 물고있다. 꼭 닫아준다.
em.close();
}
emf.close();
}
}
데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트 사용
데이터베이스 오브젝트(예: 오라클 시퀀스)
오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용
주의: allocationSize 기본값 = 50
테이블에 매핑한 USER_SEQ에서 키를 가져왔다.
Hibernate:
call next value for USER_SEQ
=================
Hibernate:
/* insert hellojpa.User
*/ insert
into
User
(name, id)
values
(?, ?)
package hellojpa;
import javax.persistence.*;
@Entity
@SequenceGenerator(
name = "USER_SEQ_GENERATOR",
sequenceName = "USER_SEQ", //매핑할 데이터베이스 시퀀스 이름
initialValue = 1, allocationSize = 1)
public class User {
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USER_SEQ_GENERATOR")
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();;
try {
// 비영속 상태
User user = new User();
user.setName("user1");
// 영속성 컨텍스트에 등록
// 커밋 전에 insert 쿼리 수행
em.persist(user);
System.out.println("=================");
// 커밋
tx.commit();
} catch (Exception e) {
// 실패하면 롤백
tx.rollback();
} finally {
// enity manager가 데이터 커넥션을 물고있다. 꼭 닫아준다.
em.close();
}
emf.close();
}
}
키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략
장점: 모든 데이터베이스에 적용 가능
단점: 성능이 안좋음
지정한 테이블에서 키를 가져왔습니다.
Hibernate:
update
MY_SEQUENCES
set
next_val=?
where
next_val=?
and sequence_name=?
=================
Hibernate:
/* insert hellojpa.User
*/ insert
into
User
(name, id)
values
(?, ?)
package hellojpa;
import javax.persistence.*;
@Entity
@TableGenerator(
name = "USER_SEQ_GENERATOR",
table = "MY_SEQUENCES",
pkColumnValue = "USER_SEQ", allocationSize = 1)
public class User {
@Id @GeneratedValue(strategy = GenerationType.TABLE, generator = "USER_SEQ_GENERATOR")
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();;
try {
// 비영속 상태
User user = new User();
user.setName("user1");
// 영속성 컨텍스트에 등록
em.persist(user);
System.out.println("=================");
// 커밋
tx.commit();
} catch (Exception e) {
// 실패하면 롤백
tx.rollback();
} finally {
// enity manager가 데이터 커넥션을 물고있다. 꼭 닫아준다.
em.close();
}
emf.close();
}
}
시퀀스용 테이블이 생성되었습니다.
데이터베이스 특징에 따라, IDENTITY, SEQUENCE, TABLE 중 하나 선택
데이터베이스를 변경해도 코드를 수정할 필요없다.
키 생성전략이 확정되지 않는 개발초기, 프로토타입 개발에 사용
DB에 따른 전략 선택
ORACLE - SEQUENCE
MySQL - IDENTITY
H2 - SEQUENCE
Hibernate:
call next value for hibernate_sequence
=================
Hibernate:
/* insert hellojpa.User
*/ insert
into
User
(name, id)
values
(?, ?)
package hellojpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();;
try {
// 비영속 상태
User user = new User();
user.setName("user1");
// 영속성 컨텍스트에 등록
em.persist(user);
System.out.println("=================");
// 커밋
tx.commit();
} catch (Exception e) {
// 실패하면 롤백
tx.rollback();
} finally {
// enity manager가 데이터 커넥션을 물고있다. 꼭 닫아준다.
em.close();
}
emf.close();
}
}
키에는 크게 2가지가 있다.
자연키는 유일하지 않는다.
전화번호에 null값이 들어갈 수 있고, 변할 수 있다.
환경이 변할 수 있다.
예시) 주민등록번호를 키로 정했는데 정부에서 금지 -> 모두 변경해야함
기본 키 제약 조건: null 아님, 유일, 변하면 안된다.
미래까지 이 조건을 만족하는 자연키는 찾기 어렵다. 대리키(대체키)를 사용하자.
예를 들어 주민등록번호도 기본 키로 적절하기 않다.
권장: Long형 + 대체키 + 키 생성전략 사용