코드 그라데이션

영속성 컨텍스트 본문

Spring/JPA 공부

영속성 컨텍스트

완벽한 장면 2023. 8. 13. 15:43

JPA에서 가장 중요한 2가지

• 객체와 관계형 데이터베이스 매핑하기 (Object Relational Mapping)

- 정적인 매핑 과정
• 영속성 컨텍스트

- 실제 동작

 

엔티티 매니저 팩토리와 엔티티 매니저

 

영속성 컨텍스트

• JPA를 이해하는데 가장 중요한 용어
• “엔티티를 영구 저장하는 환경”이라는 뜻
• EntityManager.persist(entity);

=> DB에 저장한다기 보단, 영속성 컨텍스트를 통해 엔티티를 영속화하는 개념

     (즉 이거는 DB에 저장이 아닌 영속성 컨텍스트에 저장한다.)

• 영속성 컨텍스트는 논리적인 개념
• 눈에 보이지 않는다.
• 엔티티 매니저를 통해서 영속성 컨텍스트에 접근

 

J2SE 환경 : 엔티티 매니저와 영속성 컨텍스트가 1:1

- 엔티티 매니저를 생성하면 영속성 컨텍스트가 있다고 생각하면 속 편함.

 

 

J2EE 환경, 스프링 프레임워크 같은 컨테이너 환경 : 엔티티 매니저와 영속성 컨텍스트가 N : 1


엔티티의 생명주기

 

 

비영속

비영속 상태
비영속

- 세팅만 해놓고 아직 영속성 컨텍스트에 아무것도 넣지 않은 상태

 

 

영속

영속 상태

- 아직 DB에 저장한 것 아님. 커밋 이후 저장됨

 

 

준영속, 삭제

 

 

영속성 컨텍스트의 이점

영속성 컨텍스트의 이점

 

==> 이렇게 생각하자 "데이터베이스와 애플리케이션 사이에 뭔가 중간 계층이 있다"

 

 

엔티티 조회, 1차 캐시

 

1차 캐시에서 조회

 

데이터베이스에서 조회

 

- 고객의 요청이 하나 들어와서 비즈니스가 끝나버리면 이 영속성 컨텍스트를 지운다.

- 1차 캐시도 다 날아간다.

- 이것은 데이터베이스의 한 트랜잭션 내에서만 동작한다.

 

실습

비영속 상태에서는 객체가 생성만 된 것이고,

영속에서 비로소 저장됨.

그러나, 영속 상태가 되었다고 해서(em.persist(member)) 반드시 데이터베이스에 저장되었다고 할 수는 없다

=> 즉, 커밋이 반드시 수행되어야 저장됨.

여기서 100L 조회해봤자 나오지도 않음.

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(100L);
      member.setName("Haerin");

      //영속
      System.out.println("BEFORE================");
      em.persist(member);
      System.out.println("AFTER================");
    } catch (Exception e) {
      tx.rollback();
    } finally {
      em.close();
    }
    emf.close();
  }
}

 

 

이렇게 해야 비로소 저장된 101L에 해당된 아이디와 이름이 조회됨.

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(100L);
      member.setId(101L);
      member.setName("Haerin");

      //영속
      System.out.println("BEFORE================");
//      em.persist(member); //그런데 이 때 DB에 저장되는 건 아니다.
      em.persist(member);
      System.out.println("AFTER================");

      Member findMember = em.find(Member.class, 101L);
      System.out.println("findMember.id = " + findMember.getId());
      System.out.println("findMember.name = " + findMember.getName());

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

 

두 번째 조회할 때는 DB에서 쿼리가 나가면 안 되고, 1차캐시에서 가져와야 하므로 쿼리가 한 번만 나가야한다.

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 findMember1 = em.find(Member.class, 101L);
      Member findMember2 = em.find(Member.class, 101L);

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

Hibernate: 
    select
        member0_.id as id1_0_0_,
        member0_.name as name2_0_0_ 
    from
        Member member0_ 
    where
        member0_.id=?

 


영속 엔티티의 동일성 보장

1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 

데이터베이스가 아닌 애플리케이션 차원에서 제공

=> 내가 자바 컬렉션에서 가져왔을 때 주소가 동일했던 것처럼, JPA가  == 비교를 보장해준다는 뜻. (1차 캐시 덕분)

 

실습

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 findMember1 = em.find(Member.class, 101L);
      Member findMember2 = em.find(Member.class, 101L);

      System.out.println("result = " + (findMember1 == findMember2));

      tx.commit();
    } catch (Exception e) {
      tx.rollback();
    } finally {
      em.close();
    }
    emf.close();
  }
}
13:56:09.084 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl - On call to EntityIdentifierReaderImpl#resolve, EntityKey was already known; should only happen on root returns with an optional identifier specified
13:56:09.094 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Resolving attributes for [inflearn.exjpa.Member#101]
13:56:09.095 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `name` : value = Haerin
13:56:09.095 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`name`)  - enhanced for lazy-loading? - false
13:56:09.096 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done materializing entity [inflearn.exjpa.Member#101]
13:56:09.099 [main] DEBUG org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader - Done entity load : inflearn.exjpa.Member#101
result = true
13:56:09.099 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - committing
13:56:09.099 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Processing flush-time cascades
13:56:09.104 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Dirty checking collections
13:56:09.107 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 0 insertions, 0 updates, 0 deletions to 1 objects
13:56:09.107 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
13:56:09.108 [main] DEBUG org.hibernate.internal.util.EntityPrinter - Listing entities:
13:56:09.108 [main] DEBUG org.hibernate.internal.util.EntityPrinter - inflearn.exjpa.Member{name=Haerin, id=101}
13:56:09.110 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
13:56:09.111 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
13:56:09.111 [main] DEBUG org.hibernate.internal.SessionFactoryImpl - HHH000031: Closing
13:56:09.112 [main] DEBUG org.hibernate.type.spi.TypeConfiguration$Scope - Un-scoping TypeConfiguration [org.hibernate.type.spi.TypeConfiguration$Scope@7a356a0d] from SessionFactory [org.hibernate.internal.SessionFactoryImpl@8692d67]
13:56:09.112 [main] DEBUG org.hibernate.service.internal.AbstractServiceRegistryImpl - Implicitly destroying ServiceRegistry on de-registration of all child ServiceRegistries
13:56:09.112 [main] INFO org.hibernate.orm.connections.pooling - HHH10001008: Cleaning up connection pool [jdbc:h2:tcp://localhost/~/test]
13:56:09.113 [main] DEBUG org.hibernate.boot.registry.internal.BootstrapServiceRegistryImpl - Implicitly destroying Boot-strap registry on de-registration of all child ServiceRegistries

종료 코드 0(으)로 완료된 프로세스

result = true가 출력됨.

 


엔티티 등록 : 트랜잭션을 지원하는 쓰기 지연

- 한 방에 모았다가, 커밋 시점에 보낸다.

그림으로 확인하면

 

옵션 중에 <property name = "hibernate.jdbc.batch-size" value="10" /> 이런 걸 쓰면,

이 value 크기만큼 모았다가 한 번에 쿼릴르 보내고 저장시킴

 

활용 잘 하면 옵션 하나로 기본적으로 성능을 먹고 들어갈 수 있다.

 

실습

일단 Member 클래스에 생성자 삽입

@Entity
@NoArgsConstructor
public class Member {

  public Member(Long id, String name) {
    this.id = id;
    this.name = 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;
  }

  @Id
  private Long id;
  private String name;
//Getter, Setter …
}

그리고

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 member1 = new Member(150L, "A");
      Member member2 = new Member(160L, "B");

      em.persist(member1);
      em.persist(member2); // 여기까지 영속성 컨텍스트에 쌓임
      System.out.println("==========쿼리 나가는 거 구분선==========");

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

이러면

14:23:44.523 [main] DEBUG org.hibernate.loader.entity.plan.EntityLoader - Static select for entity inflearn.exjpa.Member [NONE]: select member0_.id as id1_0_0_, member0_.name as name2_0_0_ from Member member0_ where member0_.id=?
14:23:44.542 [main] DEBUG org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator - No schema actions specified
14:23:44.542 [main] DEBUG org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator - No actions specified; doing nothing
14:23:44.542 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator - No JtaPlatform was specified, checking resolver
14:23:44.543 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformResolverInitiator - No JtaPlatformResolver was specified, using default [org.hibernate.engine.transaction.jta.platform.internal.StandardJtaPlatformResolver]
14:23:44.546 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.StandardJtaPlatformResolver - Could not resolve JtaPlatform, using default [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
14:23:44.546 [main] INFO org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
14:23:44.549 [main] DEBUG org.hibernate.service.internal.SessionFactoryServiceRegistryImpl - EventListenerRegistry access via ServiceRegistry is deprecated.  Use `sessionFactory.getEventEngine().getListenerRegistry()` instead
14:23:44.551 [main] DEBUG org.hibernate.hql.internal.QueryTranslatorFactoryInitiator - QueryTranslatorFactory: org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory@3a7704c
14:23:44.554 [main] DEBUG org.hibernate.query.spi.NamedQueryRepository - Checking 0 named HQL queries
14:23:44.554 [main] DEBUG org.hibernate.query.spi.NamedQueryRepository - Checking 0 named SQL queries
14:23:44.555 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Initializing SessionFactoryRegistry : org.hibernate.internal.SessionFactoryRegistry@54a3ab8f
14:23:44.556 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Registering SessionFactory: 6673586b-09e0-44dd-8e9c-ade941cc5636 (<unnamed>)
14:23:44.556 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Not binding SessionFactory to JNDI, no JNDI name configured
14:23:44.601 [main] DEBUG org.hibernate.stat.internal.StatisticsInitiator - Statistics initialized [enabled=false]
14:23:44.607 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
14:23:44.607 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - begin
14:23:44.612 [main] DEBUG org.hibernate.event.internal.AbstractSaveEventListener - Generated identifier: 150, using strategy: org.hibernate.id.Assigned
14:23:44.630 [main] DEBUG org.hibernate.event.internal.AbstractSaveEventListener - Generated identifier: 160, using strategy: org.hibernate.id.Assigned
==========쿼리 나가는 거 구분선==========
14:23:44.631 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - committing
14:23:44.631 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Processing flush-time cascades
14:23:44.632 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Dirty checking collections
14:23:44.636 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 2 insertions, 0 updates, 0 deletions to 2 objects
14:23:44.636 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
14:23:44.637 [main] DEBUG org.hibernate.internal.util.EntityPrinter - Listing entities:
14:23:44.637 [main] DEBUG org.hibernate.internal.util.EntityPrinter - inflearn.exjpa.Member{name=A, id=150}
14:23:44.637 [main] DEBUG org.hibernate.internal.util.EntityPrinter - inflearn.exjpa.Member{name=B, id=160}
14:23:44.644 [main] DEBUG org.hibernate.SQL - 
    /* insert inflearn.exjpa.Member
        */ insert 
        into
            Member
            (name, id) 
        values
            (?, ?)
Hibernate: 
    /* insert inflearn.exjpa.Member
        */ insert 
        into
            Member
            (name, id) 
        values
            (?, ?)
14:23:44.650 [main] DEBUG org.hibernate.SQL - 
    /* insert inflearn.exjpa.Member
        */ insert 
        into
            Member
            (name, id) 
        values
            (?, ?)
Hibernate: 
    /* insert inflearn.exjpa.Member
        */ insert 
        into
            Member
            (name, id) 
        values
            (?, ?)
14:23:44.653 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
14:23:44.653 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
14:23:44.653 [main] DEBUG org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl - HHH000420: Closing un-released batch
14:23:44.654 [main] DEBUG org.hibernate.internal.SessionFactoryImpl - HHH000031: Closing
14:23:44.654 [main] DEBUG org.hibernate.type.spi.TypeConfiguration$Scope - Un-scoping TypeConfiguration [org.hibernate.type.spi.TypeConfiguration$Scope@238ad8c] from SessionFactory [org.hibernate.internal.SessionFactoryImpl@78f5c518]
14:23:44.655 [main] DEBUG org.hibernate.service.internal.AbstractServiceRegistryImpl - Implicitly destroying ServiceRegistry on de-registration of all child ServiceRegistries
14:23:44.655 [main] INFO org.hibernate.orm.connections.pooling - HHH10001008: Cleaning up connection pool [jdbc:h2:tcp://localhost/~/test]
14:23:44.657 [main] DEBUG org.hibernate.boot.registry.internal.BootstrapServiceRegistryImpl - Implicitly destroying Boot-strap registry on de-registration of all child ServiceRegistries

종료 코드 0(으)로 완료된 프로세스

 


엔티티 수정 : 변경 감지

 

변경 감지 (Dirty Checking)

 

- JPA는 값을 변경하면 트랜잭션이 커밋 되는 시점에 무조건 변경을 반영한다고 생각하면 편함

 

 

엔티티 삭제

 

 

실습.

public class JpaMain {
  public static void main(String[] args) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
    EntityManager em = emf.createEntityManager();
//  em.persist(member); 필요가 없어요.    

    EntityTransaction tx = em.getTransaction();
    tx.begin();

    try {
      Member member = em.find(Member.class, 150L); // 현재 이름은 A
      member.setName("ZZZ");
      tx.commit();
    } catch (Exception e) {
      tx.rollback();
    } finally {
      em.close();
    }
    emf.close();
  }
}
Hibernate: 
    /* update
        inflearn.exjpa.Member */ update
            Member 
        set
            name=? 
        where
            id=?
14:36:12.691 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
14:36:12.692 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
14:36:12.692 [main] DEBUG org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl - HHH000420: Closing un-released batch
14:36:12.692 [main] DEBUG org.hibernate.internal.SessionFactoryImpl - HHH000031: Closing
14:36:12.692 [main] DEBUG org.hibernate.type.spi.TypeConfiguration$Scope - Un-scoping TypeConfiguration [org.hibernate.type.spi.TypeConfiguration$Scope@60c16548] from SessionFactory [org.hibernate.internal.SessionFactoryImpl@78f5c518]
14:36:12.693 [main] DEBUG org.hibernate.service.internal.AbstractServiceRegistryImpl - Implicitly destroying ServiceRegistry on de-registration of all child ServiceRegistries
14:36:12.693 [main] INFO org.hibernate.orm.connections.pooling - HHH10001008: Cleaning up connection pool [jdbc:h2:tcp://localhost/~/test]
14:36:12.694 [main] DEBUG org.hibernate.boot.registry.internal.BootstrapServiceRegistryImpl - Implicitly destroying Boot-strap registry on de-registration of all child ServiceRegistries

종료 코드 0(으)로 완료된 프로세스

변경됨

 

 

728x90

'Spring > JPA 공부' 카테고리의 다른 글

엔티티 매핑 1 - 객체와 테이블 매핑  (0) 2023.08.15
flush와 준영속 상태  (0) 2023.08.14
JPA의 등장과 발전  (0) 2023.08.12
SQL 중심적인 개발의 문제점  (0) 2023.08.12
JPQL 간단한 소개  (0) 2023.08.11
Comments