[JPA] 영속성 컨텍스트

ttaho·2023년 12월 7일
0

JPA

목록 보기
2/7

JPA를 공부하다보면 영속성 컨텍스트.. 영속성 컨텍스트.. 엄청 자주 나오는 단어이다. 영속성 컨텍스트에 대해 알아보자.

영속성 컨텍스트란?

영속성 컨텍스트(Persistence Context)는 엔티티를 영구 저장하는 환경 이라는 뜻이다.
EntityManager.persist(entity) 는 사실 DB에 entity를 저장한다는 뜻이 아니라, 영속성 컨텍스트에 entity를 저장한다는 뜻이다.
EntityManager 안에 영속성 컨텍스트 라는것이 있다.

엔티티의 생명주기

영속성 컨텍스트를 이해하기 위해 엔티티의 생명 주기를 알아보자.

  • 비영속 (new/transient)
    영속성 컨텍스트와 전혀 관계가 없는 새로운 상태

  • 영속 (managed)
    영속성 컨텍스트에 관리되는 상태

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

  • 삭제 (removed)
    삭제된 상태

코드로 확인해보자.

public class JpaMain {

    public static void main(String[] args) {
        // EntityManagerFactory는 데이터베이스당 1개!
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        // 트랜잭션 단위인 작업에는 entityManager를 만들어줘야한다!!
        // entityManager는 쓰레드간에 공유하면 안된다. 쓰고 버려야함.

        EntityManager entityManager = emf.createEntityManager(); // 엔티티매니저 만들기

        // JPA의 모든 데이터 변경은 트랜잭션 안에서 실행해야함.

        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();

        // JPQL은 객체지향 쿼리언어이다. JPQL은 테이블 대상이 아닌 엔티티 객체를 대상
        try{
            // 비영속 상태
            Member member = new Member();
            member.setId(1L);
            member.setName("HelloA");
            
            // 영속 상태
            entityManager.persist(member); // 영속성 컨텍스트에 저장된 상태! DB저장은 아님!
            transaction.commit(); // Insert 쿼리가 실행되어 DB에 저장됨
        } catch (Exception e){
            transaction.rollback();
        } finally {
            // close
            entityManager.close();
            emf.close();
        }

    }
}

해당 코드에서
member 라는 엔티티를 생성만 한 상태는 비영속 -> 아직 영속성 컨텍스트에 등록X
entityManager를 통해 persist 한 상태는 영속 -> 영속성 컨텍스트에 등록O

transaction.commit 메소드를 통해 DB에 member가 저장된다.

// 회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
entityManager.detach(member);

// 객체를 삭제한 상태(삭제) -> DB 에서 삭제된다.
entityManager.remove(member);

entityManager.detach를 하면 준영속 -> 영속성 컨텍스트에서 나온다.
entityManager.remove를 하면 삭제 -> DB에서 삭제가 된다.

준영속 상태로 만드는 방법

  1. entityManager.detach(entity) : 특정 엔티티만 준영속 상태로 전환
  2. entityManager.clear() : 영속성 컨텍스트를 완전히 초기화
  3. entityManager.close() : 영속성 컨텍스트를 종료

영속성 컨텍스트의 이점

  1. 1차 캐시
    데이터베이스에 접근하는 비용은 애플리케이션 서버 내부 메모리에 접근하는 시간보다 훨씬 비싸다. 따라서 조회한 데이터를 메모리에 캐싱해 두면 데이터베이스 접근 횟수를 줄여 성능을 개선할 수 있다.
  • 영속성 컨텍스트 내부의 엔티티를 보관하는 저장소를 1차 캐시라고 한다. 일반적으로 트랜잭션을 시작하고 종료할 때까지만 1차 캐시가 유효하다.
  • entityManager.find(entity) 수행 시 영속성 컨텍스트의 1차캐시를 먼저 확인하고, 없으면 DB를 조회해서 꺼내온다. 그 것을 1차 캐시에 저장하고, 반환한다.
  • 1차 캐시는 같은 entityManager에서 동일한 값을 꺼내올때 동일성이 보장된다.
Member a = em.find(Member.class, "member1"); 
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true
  1. 트랜잭션을 지원하는 쓰기 지연
    한 트랜잭션에서 커밋하기 전까지 쓰기 지연 SQL 저장소에 쿼리를 저장시킨다.
    커밋할때 해당하는 쿼리를 순차적으로 수행한다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋

최종적으로 commit시 Insert 쿼리문이 DB에 반영된다.

  1. 변경 감지
    한 트랜잭션에서 엔티티의 데이터가 수정되면 따로 update 하는 코드 없이 commit을 하게되면 데이터의 수정된것이 DB에 반영된다.
    이것은 commit시 flush가 발생하여 1차캐시의 스냅샷과 Entity를 비교한다.
    다르면 UPDATE SQL을 쓰기 지연 SQL 저장소에 생성하여 DB에 쿼리문을 날린다.

플러시(flush)란?

영속성 컨텍스트의 변경내용을 데이터베이스에 반영하는 것을 의미한다.
아래 경우에 flush가 발생한다.

  • 트랜잭션에 commit이 발생할 때
  • EntityManager의 flush 메서드를 호출했을 때
  • JPQL 쿼리가 실행될 때

플러시가 발생하면 쓰기 지연 저장소에 저장된 SQL(INSERT/UPDATE/DELETE)이 데이터베이스로 전송된다.
또한, 플러시가 발생했다고 해서 영속성 컨텍스트가 비워지는 것이 아니고, 변경 사항을 DB와 동기화 하는 것을 의미한다.
즉, flush가 발생해도 1차 캐시는 유지된다.

그런데, JPQL 쿼리가 실행될 때 flush가 발생하는 이유는 무엇일까?

em.persist(memberA); // 영속성 컨텍스트에 추가
em.persist(memberB); // 영속성 컨텍스트에 추가
em.persist(memberC); // 영속성 컨텍스트에 추가
//중간에 JPQL 실행
query = em.createQuery("select m from Member m", Member.class);
List<Member> members= query.getResultList();

위의 코드에서 memberA,B,C가 영속성 컨텍스트에 추가되어 영속 상태가 되었다.
중간에 JPQL을 실행하여 Member 테이블의 모든 데이터를 조회한다. 이때, flush가 발생하지 않으면 memberA,B,C는 조회한 데이터에 포함되지 않게된다!
그래서 JPQL 수행 전 자동으로 flush를 발생시켜 DB와 영속성 컨텍스트간의 동기화 작업을 수행한다.

플러시의 동작 과정

  1. 영속성 컨텍스트에서 변경을 감지한다 -> Dirty Checking
  2. 수정이 발생한 Entity를 쓰기 지연 SQL 저장소에 등록한다.
  3. 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스로 전송한다.

주의할 점은 DB에는 트랜잭션이라는 작업 단위가 존재하기 때문에 한 트랜잭션 내에서 flush가 아무리 발생해도 DB commit은 발생하지 않는다는 점이다.
DB commit은 해당 트랜잭션이 commit 될 때 발생한다.

요약하면 flush는

  1. 영속성 컨텍스트를 비우지 않는다.
  2. 영속성 컨텍스트의 변경내용을 DB에 동기화하는 작업이다.
  3. 트랜잭션이라는 작업 단위가 중요하기 때문에 커밋 직전에만 동기화 하면 된다.
profile
백엔드 꿈나무

0개의 댓글