코드 그라데이션

<보충설명> 기본키 매핑 본문

Spring/JPA 공부

<보충설명> 기본키 매핑

완벽한 장면 2023. 8. 16. 11:36

보충

이 IDENTITY 전략은 Id를 내가 직접 넣으면 안 된다.

Id가 NULL로 날아오면 그 때 DB에서 Id 값을 직접 세팅해준다.

 

뭐가 문제냐,id 값을 알 수 있는 시점이 DB에 값이 들어가봐야 안다는 것.그런데 영속성 컨텍스트에서 관리가 되려면 무조건 PK 값이 있어야 했다.

 

그래서 어떤 제약이 생기느냐...JPA는 PK를 모르니 값을 넣을 수 있는 방법이 없다는 것.

 

울며 겨자먹기로, IDENTITY 전략에서만 em.persist() 호출하자마자 그 시점에

 

Hibernate: 
    /* insert inflearn.exjpa.Member
        */ insert 
        into
            Member
            (id, name) 
        values
            (null, ?)

이렇게 INSERT 쿼리를 날려버린다.

(보통 커밋시점에 INSERT 쿼리 날아간다고 함)

 

아이디 출력해서 로그를 확인해보면

======================
16:31:18.246 [main] DEBUG org.hibernate.engine.spi.ActionQueue - Executing identity-insert immediately
16:31:18.251 [main] DEBUG org.hibernate.SQL - 
    /* insert inflearn.exjpa.Member
        */ insert 
        into
            Member
            (id, name) 
        values
            (default, ?)
Hibernate: 
    /* insert inflearn.exjpa.Member
        */ insert 
        into
            Member
            (id, name) 
        values
            (default, ?)
16:31:18.256 [main] DEBUG org.hibernate.id.IdentifierGeneratorHelper - Natively generated identity: 1
16:31:18.256 [main] DEBUG org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl - HHH000387: ResultSet's statement was not registered
member.id = 1 // 이렇게 잘 나와있고
======================

이것도 잘 들어가 있음.

 

Select 쿼리는 왜 안 나오느냐?

Insert 쿼리를 했을 때 이런 경우에

값을 넣고 리턴을 받는 로직이 내부적으로 이미 다 구현이 되어 있다.

그래서 DB에 INSERT하는 시점에 이 Id 값을 바로 알 수 있는 것.

 

따라서. 모아서 INSERT 하는 게 IDENTITY 전략에서는 불가능하다!

 

한 트랜잭션 안에서 INSERT 쿼리가 여러번 나간다고 해서 성능에 비약적인 차이를 초래하거나 하진 않으니

크게 상관은 없는 부분이다.

 

만약 SEQUENCE로 바꾸거나 하면 실제 커밋하는 시점에 쿼리가 날아간다.

 

 

 


SEQUENCE

SEQUENCE 전략 - 매핑

일단 이렇게 바꾸고 실행을 시켜보면,

    drop table if exists Member CASCADE 
Hibernate: 
    
    drop table if exists Member CASCADE 
16:44:52.082 [main] INFO org.hibernate.orm.connections.access - HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@85ec632] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
16:44:52.084 [main] DEBUG org.hibernate.SQL - 
    
    drop sequence if exists MEMBER_SEQ
Hibernate: 
    
    drop sequence if exists MEMBER_SEQ
16:44:52.087 [main] DEBUG org.hibernate.SQL - create sequence MEMBER_SEQ start with 1 increment by 1
Hibernate: create sequence MEMBER_SEQ start with 1 increment by 1
16:44:52.087 [main] INFO org.hibernate.orm.connections.access - HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@1c4ee95c] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
16:44:52.089 [main] DEBUG org.hibernate.SQL - 
    
    create table Member (
       id bigint not null,
        name varchar(255) not null,
        primary key (id)
    )
Hibernate: 
    
    create table Member (
       id bigint not null,
        name varchar(255) not null,
        primary key (id)
    )
16:44:52.091 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator - No JtaPlatform was specified, checking resolver
16:44:52.092 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformResolverInitiator - No JtaPlatformResolver was specified, using default [org.hibernate.engine.transaction.jta.platform.internal.StandardJtaPlatformResolver]
16:44:52.096 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.StandardJtaPlatformResolver - Could not resolve JtaPlatform, using default [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
16:44:52.096 [main] INFO org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
16:44:52.100 [main] DEBUG org.hibernate.service.internal.SessionFactoryServiceRegistryImpl - EventListenerRegistry access via ServiceRegistry is deprecated.  Use `sessionFactory.getEventEngine().getListenerRegistry()` instead
16:44:52.102 [main] DEBUG org.hibernate.hql.internal.QueryTranslatorFactoryInitiator - QueryTranslatorFactory: org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory@7668d560
16:44:52.105 [main] DEBUG org.hibernate.query.spi.NamedQueryRepository - Checking 0 named HQL queries
16:44:52.105 [main] DEBUG org.hibernate.query.spi.NamedQueryRepository - Checking 0 named SQL queries
16:44:52.106 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Initializing SessionFactoryRegistry : org.hibernate.internal.SessionFactoryRegistry@54e81b21
16:44:52.108 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Registering SessionFactory: d66652e6-f53e-4616-bd06-30c21817f735 (<unnamed>)
16:44:52.108 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Not binding SessionFactory to JNDI, no JNDI name configured
16:44:52.164 [main] DEBUG org.hibernate.stat.internal.StatisticsInitiator - Statistics initialized [enabled=false]
16:44:52.169 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
16:44:52.169 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - begin
=======================
16:44:52.174 [main] DEBUG org.hibernate.SQL - 
    call next value for MEMBER_SEQ
Hibernate: 
    call next value for MEMBER_SEQ
16:44:52.177 [main] DEBUG org.hibernate.id.enhanced.SequenceStructure - Sequence value obtained: 1
16:44:52.178 [main] DEBUG org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl - HHH000387: ResultSet's statement was not registered
16:44:52.181 [main] DEBUG org.hibernate.event.internal.AbstractSaveEventListener - Generated identifier: 1, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
member.id = 1
=======================

 

create sequence MEMBER_SEQ start with 1 increment by 1 이 부분이 있다.

1부터 시작하고 1씩 증가시키라는 것

그런데 이 아이디도 시퀀스는 DB에서 관리하기 때문에 DB를 까봐야 알 수 있다.

이렇게

이 값을 애플리케이션으로 가져와야 함.

em.persist(member);를 하려면 일단 영속성 컨텍스트에 넣어야 하는데,

그럼 반드시 PK를 알아야 해서.시퀀스에서 미리  pk를 가져옴

그다음에 영속성 컨텍스트에 저장.

아직 DB에 인서트 쿼리는 안 날아가고 실제 커밋 시점에 날아간다.

 

자꾸 네트워크를 왔다갔다 해야 하지 않느냐. 성능 문제는??

 

이걸 활

SEQUENCE - @SequenceGenerator

 

@Entity
@NoArgsConstructor
@SequenceGenerator(
    name = "MEMBER_SEQ_GENERATOR",
    sequenceName = "MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
    initialValue = 1, allocationSize = 50)
public class Member {

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

 

이걸 저장을 할 때마다 next call로 가져오면 성능 문제가 나타날 수 있기 때문에

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();
      member2.setUsername("B");

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

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

      em.persist(member1);
      em.persist(member2);
      em.persist(member3);

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

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

 

allocationSize = 50 이렇게 미리 DB에 50개를 얹어놓고, 메모리는 1씩 쓰는 것.

DB에 미리 올려놓고 메모리에서는 그 개수만큼 쓰는 방식이다.

 

em.persist는 빼고 일단 실행 눌러보면

  drop table if exists Member CASCADE 
Hibernate: 
    
    drop table if exists Member CASCADE 
17:22:34.525 [main] INFO org.hibernate.orm.connections.access - HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@1a1d3c1a] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
17:22:34.527 [main] DEBUG org.hibernate.SQL - 
    
    drop sequence if exists MEMBER_SEQ
Hibernate: 
    
    drop sequence if exists MEMBER_SEQ
17:22:34.531 [main] DEBUG org.hibernate.SQL - create sequence MEMBER_SEQ start with 1 increment by 50
Hibernate: create sequence MEMBER_SEQ start with 1 increment by 50
17:22:34.531 [main] INFO org.hibernate.orm.connections.access - HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@6ac4944a] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
17:22:34.533 [main] DEBUG org.hibernate.SQL - 
    
    create table Member (
       id bigint not null,
        name varchar(255) not null,
        primary key (id)
    )
Hibernate: 
    
    create table Member (
       id bigint not null,
        name varchar(255) not null,
        primary key (id)
    )

create sequence MEMBER_SEQ start with 1 increment by 50 이 메시지 확인할 수 있다.

 

DB열어보면

이렇다

 

여기서 em.persist(member1); 만 풀어서 실행해보면

Hibernate: 
    call next value for MEMBER_SEQ 이게 두 번 호출이 되고

바뀐다.

이제 다 풀어서 3개 다 em.persist() 하면

em.persist(member1); // 1, 51
em.persist(member2); // 메모리에서
em.persist(member3); // 메모리에서

 51번을 만나는 순간 넥스트 콜이 한 번 더 실행된다는 느낌.

 

일단 이정도로 이해

 

@TableGenerator 속성에서도 똑같다.

이거 말이다.

 

간단히 정리하자면

Table에 AUTO_INCREMENT를 50개를 미리 쫙 해놓고 웹 서버에서 50개를 쭉 써가는 방식이라고 이해하면 편하다.

 

미리 값을 올려두는 방식이기 때문에 서버가 여러대이더라도 괜찮다.

 

 

728x90
Comments