JPA 영속성(persistence context)


JPA 영속성(persistence context)

영속성 컨텍스트는 엔티티를 영구적으로 저장하는 환경이라는 뜻으로 엔티티 매니저는 엔티티를 저장하거나 조회 할때 이 영속성 컨텍스트에 엔티티를 보관하고 관리한다.
영속성 컨텍스트는 논리적인 개념이기 때문에 해당 부분을 이해하지 않으면 데이터를 저장을 했는데 저장이 안되거나 하는 경험을 하게 된다.
영속성 컨텍스트에 대해 하나씩 알아보자.

1. 엔티티 매니저 팩토리(EntityManagerFactory)와 엔티티 매니저(EntityManager)

데이터베이스를 하나만 사용 하는 경우 엔티티 매니저 팩토리는 하나만 생성 하고 필요할 경우 엔티티 매니저를 생성 한다.

//엔티티 매니저 팩토리 생성 
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaExam");
//엔티티 매니저 생성
EntityManager em = emf.createEntityManager();
  • EntityManagerFactory : 생성시 비용이 매우 큼. Thread safety 하다.
  • EntityManager : 생성시 비용이 거의 없음. Thread safety 하지 못하기 때문에 공유 하면 안됨.

2. 엔티티의 4가지 생명주기

상태 설명
비영속 영속성 컨텍스트와 전혀 관계 없는 상태
영속 영속성 컨텍스트에 저장된 상태
준영속 영속성 컨텍스트에 저장되었다가 분리된 상태
삭제 삭제된 상태
  • 비영속

    비영속 상태는 생성된 엔티티 객체를 저장하지 않은 상태를 말하며 데이터 베이스와 전혀 상관없는 상태를 말한다.

              //엔티티 객체만 생성
              Member member = new Member();
              member.setId("jpa1");
              member.setMemberName("홍길동");
              member.setAge(20);
              member.setAddress("Hello Java JPA");
    
  • 영속

    생성된 엔티티 객체를 엔티티 매니저를 통하여 영속성 컨텍스트에 저장된 상태.

             //엔티티 매니저에 등록
             em.persist(member);
    
  • 준영속

    영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 더이상 관리 하지 않도록 제외 시킨 상태를 준영속 상태라고 말한다.

          //엔티티 매니저에 등록 제외
          em.detach(member);
    
  • 삭제

    엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제 처리한 상태

          //엔티티 매니저에 삭제
          em.remove(member);
    

3. 영속성 컨텍스트의 특징

JPA는 이 영속성 컨텍스트를 통하여 엔티티를 관리 하게 되며 이 영속성 컨텍스트를 이용하여 엔티티를 관리 하면 아래와 같은 장점이 있다.

  • 1차 캐시

    영속성 컨텍스트는 내부에 캐시를 가지고 있는데 이를 1차 캐시라고 한다. 엔티티를 생성 하여 엔티티 매니저에 persist() 하게 되면 이 영역에 저장되어 관리 되게 된다.
    이때 데이터베이스에는 아직 저장 되지 않은 상태 이다.

    • 1차 캐시 등록

       Member member = new Member();
       member.setId("jpa1");
       member.setMemberName("홍길동");
       member.setAge(20);
       member.setAddress("Hello Java JPA");
      
       //등록
       em.persist(member); 
      
    • 1차 캐시 등록을 하게 되면 영속 컨택스트에 아래와 같이 등록 된다.

      @ID Entity
      jpa1 Member
         
    • 1차 캐시 조회

      em.find(Member.class, “jpa1”); 을 통하여 데이터를 조회 했을 때 1차 캐시에서 엔티티를 찾게 된다.
      만약 1차 캐시에서 찾고자 하는 “jpa1” 엔티티가 없다면 데이터베이스를 조회 하여 이 1차 캐시에 보관 하고 이 엔티티를 조회 하게 된다.

         Member member = new Member();
         member.setId("jpa1");
         member.setMemberName("홍길동");
         member.setAge(20);
         member.setAddress("Hello Java JPA");
        
         //등록
         em.persist(member);
        
         // 1차 캐시 에서 조회 하여 엔티티를 반환.
         Member member = em.find(Member.class, "jpa1");
        
         // 1차 캐시에 엔티티가 없기 때문에 데이터베이스에서 찾아서 1차 캐시에 등록 후 엔티티를 반환.
         Member member2 = em.find(Member.class, "jpa2");
      
  • 동일성 보장

    아래 코드로 조회한 엔티티 인스턴스는 동일할까? 아래 인스턴스를 비교 해보면 결과는 참이다.
    영속성 컨텍스트는 엔티티의 동일성을 보장한다.

      Member member1 = em.find(Member.class, "jpa1");
      Member member2 = em.find(Member.class, "jpa1");
      System.out.println(member1 == member2); //결과 == true
    
  • 쓰기 지연

    엔티티 매니저는 트랜젝션을 커밋 하기 직전까지 엔티티를 영속성 컨텍스트 영역에 저장 한다.
    Commit 명령이 주어 졌을 때 비로서 데이터베이스에 INSERT 명령을 처리 하게 된다. 이를 쓰기 지연이라고 한다.

      public static void main( String[] args )
      {
          //엔티티 매니저 팩토리 생성 
          EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaExam");
          //엔티티 매니저 생성
          EntityManager em = emf.createEntityManager();
          //엔티티 트렌젝션 생성
          EntityTransaction tx = em.getTransaction();
          try { 
              //트랜잭션 시작
              tx.begin();
              Member member1 = new Member();
              member1.setId("jpa1");
              member1.setMemberName("홍길동"); 
                
              Member member2 = new Member();
              member2.setId("jpa2");
              member2.setMemberName("이순신");
                
              em.persist(member1);
              em.persist(member2);
              //영속성 컨텍스트 영역에 저장된 상태 이며 데이터베이스에는 저장되지 않음.
                
              tx.commit();//데이터 베이스에 INSERT 명령어를 보냄.
          } catch (Exception e) {
              e.printStackTrace();
              tx.rollback(); //트랜잭션 롤백
          } finally {
             em.close(); //엔티티 매니저 종료
          }
          emf.close(); //엔티티 매니저 팩토리 종료
      }   
    
  • 엔티티 수정(변경 감지)

    SQL을 사용하는 경우 데이터가 변경 되었을 때 SQL을 직접 작성 하여 변경을 해야 한다.
    만약 여러 항목의 데이터를 변경 해야 할 경우에는 매번 SQL을 변경 해줘야 하는 번거로운 작업이 될 수 있다.

    하지만 JPA에는 update() 명령어가 존재 하지 않는다. 영속성 컨텍스트 영역을 이용하여 엔티티를 관리 하기 때문에
    이 엔티티를 조회 하여 값을 변경하고 트렌젝션을 커밋 함으로서 데이터 수정이 완료 된다. 이를 변경 감지 라고 한다.

    JPA 에서 변경 감지는 어떻게 이루어 지는 것일까?

    JPA는 엔티티를 영속성 컨텍스트 영역에 보관 할때 변경되기 이전의 상태를 복사하여 저장 해둔다.
    이를 스냅샷이라고 하는데 commit() 명령어가 수행 될때 스냅샷과 엔티티를 비교 하여 변경된 엔티티를 찾아서 수행 한다.

  • 엔티티 삭제

    엔티티 등록과 반대로 em.remove() 명령어가 수행 되면 영속성 영역에서 엔티티를 삭제 하고 commit() 명령어가 수행 될때 삭제 SQL을 데이터베이스로 전달 하게 된다.

      Member member = em.find(Member.class, "jpa1");
      em.remove(member); // 영속성 컨텍스트 영역에서 엔티티 제거
      tx.commit();       // 데이터 베이스에 delete() 명령어 전달
    
  • 플러시

    플러시는 영속성 컨텍스트 영역의 엔티티를 데이터베이스에 반영(commit) 하는 명령이다. 플러시 하는 방법은 아래 3가지로 사용 할 수 있다.

    • em.flush() 직접 호출(거의 사용하지 않음.)
    • tx.commit() 트랜잭션 커밋 시 플러시(flush) 자동 호출
    • JPQL 쿼리 실행 시 플러시(flush) 자동 호출
    • 플러시 모드 옵션
      • FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 플러시 실행
      • FlushModeType.COMMIT : 커밋 실행시에만 플러시 실행

4. 준영속

준영속 상태는 저장되어 있던 영속성 컨텍스트 영역에서 엔티티가 분리 된 상태를 말한다.
거의 비영속 상태이기 때문에 영속성 컨텍스트의 관리를 전혀 받지 못한다.

  • 특징
    • 거의 비영속 상태
    • 한번 영속성 상태 였기 때문에 식별자 값이 존재 한다.
    • 영속성 컨텍스트 영역에서 제외 되었기 때문에 지연 로딩(Lazy Loading) 불가
  • 준영속 상태로 변경 하는 방법
    • em.detach(entity) : 특정 엔티티만 준영속 상태로 변경
    • em.clear() : 영속성 컨텍스트를 완전 초기화
    • em.close() : 영속성 컨텍스트 종료
  • 준영속 상태를 영속 상태로 변경 하는 방법
    • em.merge(entity)
      Member member = new Member();
      member.setId("jpa1");
      member.setMemberName("홍길동");
      member.setAge(20);
      member.setAddress("Hello Java JPA");
        
      //영속성 컨텍스트 등록
      em.persist(member); 
      
      //준영속 상태로 변경(영속성 컨텍스트에서 삭제됨)
      em.detach(member);
        
      //영속 상태로 변경
      em.merge(member);
    

마무리

JPA 영속성에 대해서 정리 해 보았다. JPA 가 영속성 컨텍스트를 활용 하여 어플리케이션과 데이터 베이스 사이에서 엔티티를 어떻게 보관 하고 관리 하는지에 대해서 개념을 잡을 수 있었다. 다음에는 엔티티와 테이블을 어떻게 매핑 하고 설계 하는지 정리 해보자.

Back to blog