Lecture-JPA : 영속성 컨텍스트

박건희·2021년 10월 2일
0

JPA

  • 학습목표
    • EntityManagerFactory, EntityManager
    • 영속성 컨텍스트
    • Entity LifeCycle

Entity, EntityManagerFactory, EntityManager

  • Entity
    • RDB의 Table과 매핑되는 객체

  • EntityManagerFactory

    • Entity를 관리하는 EntityManager를 생산하는 공장

    • 요청당 1개의 EntityManager 생성

    • 생성 비용이 크기 때문에 Application 로딩 시점에 생성해 EntityManager들간 공유하도록 설계

    • Thread Safe

  • EntityManager

    • EntityManager는 Entity를 저장,수정,삭제,조회 (CRUD) 등 Entity와 관련된 모든 일 처리
    • Thread Unsafe!
      -> 여러 thread에서 동시에 접근할 경우 동시성 이슈 발생 가능
    • EntityManager는 트랜잭션을 시작할 때 connection을 획득
    • 트랜잭션 완료되면 재사용 불가하도록 닫아야함

영속성 컨텍스트

  • JPA를 이용하는데 가장 중요한 요소

    • Entity를 영구 저장하는 환경

    • EntityManager는 Entity를 영속성 컨텍스트에 보관하고 관리

    • persist()를 통해 Entity가 영속성 컨텍스트로 들어감

  • 특징

    • 영속성 컨텍스트와 식별자 값
      • 영속성 컨텍스트 안에서 관리되는 Entity는 식별자 값(pk: @Id)를 반드시 가져야함
      • Key-value(Id:Entity)로 Entity를 관리하기 때문
    • 영속성 컨텍스트와 DB 저장
      • JPA는 트랜잭션을 commit하는 순간 flush가 영속성 컨텍스트에 새로 저장된 Entity를 DB에 반영(FLUSH)
      • flush는 영속성 컨텍스트의 변경 내용을 DB에 동기화하는 작업. 등록, 수정, 삭제한 Entity를 DB에 반영
    • 영속성 컨텍스트가 엔티티를 관리함으로서 얻는 이점
      • 1차 캐시
      • 동일성 보장 : transaction Commit시 1차캐시내용 = 쓰기지연저장소의 쿼리문 실행결과 DB에 반영된 내용
      • 트랜잭션을 지원하는 쓰기 지원
      • 변경 감지 (persist(Entity) 되어 영속성 컨텍스트가 관리하는 Entity는 Transaction commit시 update문이 쓰기지연저장소에 저장되고 flush되어 DB에 반영됨)
      • 지연 로딩
    • 1차캐시 / 쓰기지연저장소
      • 쓰기지연저장소 = 쿼리문 저장소
  • LifeCycle

    • 비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 업는 상태

    • 영속(managed) : 영속성 컨텍스트에 저장된 상태
      - persist() : 비영속->영속

    • 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태

      • detatch() : 준영속상태
      • clear() : 영속성 컨텍스트의 모든 Entity -> 준영속 + 쓰기지연저장소 초기화
      • close() : 영속성 컨텍스트 종료, Entity들을 준영속으로
        * merge() : 준영속->영속
    • 삭제(removed) : 삭제된 상태

      • remove() : 영속화된 Entity를 1차캐시에서 삭제, Entity를 삭제하는 DELETE 쿼리가 쓰기저장지연소에 등록됨

      비영속 : 객체가 영속성 컨텍스트, DB와 무관한 상태

      Customer customer = new Customer();
      customer.setId(1L);
      customer.setFirstName("dsf)";
      customer.setLastName("park");

      영속

        Customer customer = new Customer();
      customer.setId(1L);
      customer.setFirstName("dsf)";
      customer.setLastName("park");
      // 객체를 영속성 컨텍스트에서 관리
      em.persist(customer);

      준영속

      // 영속상태의 entity를 영속성 컨텍스트에서 분리
      em.detach(customer);
      // 영속상태의 모든 객체를 영속성 컨텍스트에서 분리
      em.clear();
      // 영속성 컨텍스트 종료
      em.close();

      삭제

      // customer Entity를 영속성 컨텍스트에서 분리하고, DB에서도 삭제
      em.remove(customer)

      영속성 컨텍스트 이해

      EntityManager em = emf.createEntityManager();  //1) entityManager 생성
      
      EntityTransaction transaction = em.getTransaction(); // 2) 트랜잭션 획득
      transaction.begin();   //3) 트랜잭션 begin
      
      Customer customer = new Custoemr();  //4-1) 비영속 상태
      customer.setId(1L);
      customer.setFirstName("asdf");
      customer.setLastName("Park");
      
      em.persist(customer);  //4-2) 영속화
      // 1차 캐시와 쓰기지연저장소에 등록됨 
      transaction.commit();  // 5) 트랜잭션 commit
      // 트랜잭션이 커밋되는 순간 쿼리가 수행됨. flush되며 DB와 동기화됨

    • 조회
      1) 1차 캐시를 이용한 조회

         @Test
         void 조회_1차캐시_이용(){
            EntityManager em = emf.createEntityManager(); 
            
            EntityTransaction transaction = em.getTransaction();
      		transaction.begin();  
            
            Customer customer = new Custoemr();  
      		customer.setId(1L);
      		customer.setFirstName("asdf");
      		customer.setLastName("Park");
            
            em.persist(customer);  
      			    transaction.commit();  
           
      				Customer entity = em.find(Customer.class, 1L);  // 1차 캐시에서 조회
            log.info("{} {}", entity.getFirstName(), entity.getLastName());
         }

      1차캐시에 있으면(em.find()) DB에 쿼리를 날지지 않고, 1차캐시에서 조회

      2) DB를 이용한 조회

      @Test
      void 조회_1차캐시_이용(){
      	EntityManager em = emf.createEntityManager(); 
         
         EntityTransaction transaction = em.getTransaction();
      		transaction.begin();  
         
         Customer customer = new Custoemr();  
      		customer.setId(1L);
      		customer.setFirstName("asdf");
      		customer.setLastName("Park");
         
         em.persist(customer);  
      			    transaction.commit();  
         
         em.clear(); // 영속성 컨텍스트 초기화(모든 entity 준영속, 쓰기저장지연소 초기화)
        
      				Customer entity = em.find(Customer.class, 1L);  // 1차캐시에 데이터 없음. DB 조회하여 1차캐시에 캐싱.
         log.info("{} {}", entity.getFirstName(), entity.getLastName());
         em.find(Customer.class, 1L);  // 캐싱된 1차캐시 사용
      }

      Entity가 준영속화되어 1차캐시에 없으면 DB조회후 Entity 만들어 1차캐시에 삽입.
      이후 요청은 1차캐시에 조회 후 반환됨


    • 수정

      @Test
      void 수정(){
      	EntityManager em = emf.createEntityManager(); 
         
         EntityTransaction transaction = em.getTransaction();
      		transaction.begin();  
         
         Customer customer = new Custoemr();  
      		customer.setId(1L);
      		customer.setFirstName("asdf");
      		customer.setLastName("Park");
         
         em.persist(customer);  
      			    transaction.commit();  
         
        transaction.begin();
        
      				Customer entity = em.find(Customer.class, 1L);  
         entity.setFirstName("sdf");
         entity.setLastName("Kim");
         
         //em.update(entity);  
         transaction.commit();
      }

      persist(entity)로 영속화된 Entity는 최소 영속화 시점의 상태가 스냅샷으로 기록되어, commit시 스냅샷과 현재 Entity를 비교해 변경된 Entity의 update 쿼리가 쓰기지연저장소에 저장됨. 이후 쓰기저장지연소의 쿼리문을 flush하여 DB 반영

    • 삭제

      @Test
      void 삭제(){
      	EntityManager em = emf.createEntityManager(); 
         
         EntityTransaction transaction = em.getTransaction();
      			transaction.begin();  
         
         Customer customer = new Custoemr();  
      			customer.setId(1L);
      			customer.setFirstName("asdf");
      			customer.setLastName("Park");
         
         em.persist(customer);  
      			    transaction.commit();  
         
         transaction.begin();
        
      				Customer entity = em.find(Customer.class, 1L);  
         em.remove(entity);
         
         transaction.commit(); //flush -> DETELT 쿼리
      }

    기타

  • Entity가 영속상태에 있으면 조회시 1차캐시로(쿼리 X)

  • 준영속상태(/비영속상태라면?) DB SELECT 쿼리 실행하여 Entity를 만들고 1차캐시에 저장,
    1차캐시의 Entity 반환.
    이후 질의에서는 1차캐시의 Entity반환

  • Dirty check : 최초 영속상태로 등록될때의 스냅샷과 비교하여 최종 tranaction Commit(flush) 직전에 변경된 내용이 있다면 UPDATE 쿼리 생성
    영속성컨텍스트에서 관리하는 Entity에만 해당됨

    참고링크

    JPA 영속성 컨텍스트와 Entity LifeCycle
    영속성 컨텍스트

0개의 댓글