Spring/JPA

기본 키 매핑

느리지만 꾸준하게 2022. 4. 14. 09:01

Member class 이렇게 해서 시작해보자.

@Entity
public class Member {

    @Id
    private Long id;

    @Column(name = "name", nullable = false)
    private String username;

    public Member() {
        
    }
}

 

사용할 수 있는 Annotation은 크게 두 가지가 있다.

  • @Id
  • @GeneratedValue

 

기본 키 매핑 방법

  • 직접 할당: @Id만 사용
  • 자동 생성(@GeneratedValue)

 

 

 

id를 string으로 해놓고

id를 직접 할당한다 그러면 아래와 같이 작성

// Member class

package hellojpa;

import javax.persistence.*;
@Entity
public class Member {

    @Id
    private String id;

    @Column(name = "name", nullable = false)
    private String username;

    public Member() {

    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

JpaMain class

package hellojpa;

import org.hibernate.Hibernate;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;

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 {
            Member member = new Member();
            member.setId("ID_A");
            member.setUsername("C");

            em.persist(member);

            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

GenerationType.AUTO는 DB방언에 따라 자동으로 생성

@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private String id;

    @Column(name = "name", nullable = false)
    private String username;

    public Member() {

    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

 

 

 

  • IDENTITY: 데이터베이스에 위임
  • 주로 MYSQL, PostgreSQL, SQL Server, DB2에서 사용(DB2에서 사용) => mysql의 AUTO_INCREMENT
package hellojpa;

import javax.persistence.*;

@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private String id;

    @Column(name = "name", nullable = false)
    private String username;

    public Member() {

    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

아래 구문을 mysql 방언으로 바꾸면

 

Hibernate: 
    
    create table Member (
       id varchar(255) generated by default as identity,
        name varchar(255) not null,
        TEAM_ID bigint,
        primary key (id)
    )

아래와 같이 이렇게 빌드값이 나온다.

Hibernate: 
    
    create table Member (
       id varchar(255) not null auto_increment,
        name varchar(255) not null,
        TEAM_ID bigint,
        primary key (id)
    ) engine=MyISAM

 

 

 

 

 

 

 

 

 

  • SEQUENCE: 데이터베이스 시퀀스 오브젝트 사용, ORACLE
  • @SequenceGenerator 필요

오브젝트를 만들어낸다.

Hibernate: 
    
    drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate:

이후에 call을 해서 값을 불러온다. 

Hibernate: 
    call next value for hibernate_sequence

id를 String에서 Long으로 바꿔주고 빌드 한번 해주고

package hellojpa;

import javax.persistence.*;

@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @Column(name = "name", nullable = false)
    private String username;

    public Member() {

    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

persistence.xml에서 ddl.auto값을 create 에서 none으로 바꿔준다음 다시 돌려준다.

그러면 아래와 같이 h2 db에 세팅이 된다.

<property name="hibernate.hbm2ddl.auto" value="none" />

 

이제 SequenceGenerator를 써보면

@Entity
@SequenceGenerator(name = "member_sqe_generator", sequenceName = "member_seq")
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "member_sqe_generator")
    private Long id;

    @Column(name = "name", nullable = false)
    private String username;

    public Member() {

    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

 

persistence.xml 

<property name="hibernate.hbm2ddl.auto" value="create" />

 

 

 

TABLE 전략

  • TABLE: 키 생성용 테이블 사용, 모든 DB에서 사용
  • @TableGenerator 필요
  • AUTO: 방언에 따라 자동 지정, 기본값

 

  • 키 생성 전용 테이블을 하나 ㅁ나들어서 데이터베이스 시퀀스를 흉내내는 전략
  • 장점 : 모든 데이터베이스에 적용 가능
  • 단점 : 테이블을 직접 사용하니까 성능이 좋지 않음(최적화가 되지않음)

TableGenerator을 직접 써보면 빌드기 아래와 같이 나온다.

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

    @Column(name = "name", nullable = false)
    private String username;

    public Member() {

    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

 

계속 실행시키면 NEXT_VAL이 증가하게 될 것이다.

Hibernate: 
    
    create table MY_SEQUENCES (
       sequence_name varchar(255) not null,
        next_val bigint,
        primary key (sequence_name)
    )

 

TableGenerator - 속성 중에서 아래 두개가 중요하다.

 

권장하는 식별자 전략

  • 기본 키 제약 조건: null 아니고 유일하면서 변하면 안된다.
  • 미래까지 조건을 만족하는 자연키(주민번호, 전화번호)는 찾기 어렵다. 대리키(대체키)를 이용하자.
  • 예를 들어 주민등록번호도 기본 키로 적절하지 않고
  • 권장: Long형 + 대체키(ex) UUID) + 키 생성전략 사용

 

 

다시 IDENTITY 전략으로 넘어오고

여기서 일단 영속성 파트를 참고해보자. 1차 캐시 파트 부분에서 @Id가 db에 있는 pk 값이 된다.

하지만 Identity는 db에 넣기 전까지 이 값(member1)을 모른다.

Member class

package hellojpa;

import javax.persistence.*;

@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name", nullable = false)
    private String username;

    public Member() {

    }

}

그래서 IDENTITY 전략에서만 예외적으로 

아래를 호출하자마자 db에 insert 쿼리를 날린다. 

em.persist(member);
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (id, name) 
        values
            (null, ?)

아래와 같이 실행시켜주면 db가 생성한 값을 읽어온다.

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 {
            Member member = new Member();
            member.setUsername("C");


            System.out.println("=================");
            em.persist(member);
            System.out.println("member.id = " + member.getId());
            System.out.println("=================");

            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

만약 IDENTITY를 설정하지 않고 SEQUENCE나 다른 전략을 쓰게 되면 persist가 아닌 실제 commit하는 부분에서 쿼리가 날라가게 된다.

 

 

 

 

 

allocationSize = 1을 주는 아래와 같은 매핑전략을 쓰게되면 1부터 시작해서 1씩 증가시켜라고 한다.

sequence object가 1씩 증가시켜라고 세팅을 해준다.

package hellojpa;

import javax.persistence.*;

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

    @Column(name = "name", nullable = false)
    private String username;

    public Member() {

    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}
Hibernate: create sequence hibernate_sequence start with 1 increment by 1

 

 

 

 

allocationSize = 50으로 주고 해보자.

package hellojpa;

import javax.persistence.*;

@Entity
@TableGenerator(
        name = "MEMBER_SEQ_GENERATOR",
        table = "MY_SEQUENCES",
        pkColumnValue = "MEMBER_SEQ", allocationSize = 50)
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
            generator = "MEMBER_SEQ_GENERATOR")
    private Long id;

    @Column(name = "name", nullable = false)
    private String username;

    public Member() {

    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

 

// DB SEQ = 1 | 1

// DB SEQ = 51 | 2

// DB SEQ = 51 | 3

 

 

51번이 일어난 다음에 next call이 일어난다.

나중에 다시 정리를 해본다.

package hellojpa;

import org.hibernate.Hibernate;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;

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 {
            Member member1 = new Member();
            member1.setUsername("A");

            Member member2 = new Member();
            member1.setUsername("B");

            Member member3 = new Member();
            member1.setUsername("C");

            System.out.println("=================");

            em.persist(member1); // 1, 51
            em.persist(member2); // MEMORY
            em.persist(member3); // MEMORY

            System.out.println("member1 = " + member1.getId());
            System.out.println("member2 = " + member2.getId());
            System.out.println("member3 = " + member3.getId());
            System.out.println("=================");

            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

<출처 김영한: 자바 ORM 표준 JPA 프로그래밍 - 기본편 >

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

 

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

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com