본문 바로가기
Language/Java

[JPA] JPA의 영속성 컨텍스트란?

by jaee_ 2022. 6. 25.

JPA 를 이해하기 위해선  EntityManagerFactory 와 EntityManager 를 먼저 알아야한다. 


EntityManagerFactory & EntityManager

  • EntityManagerFactory 는 이름 그대로 EntityManager를 만드는 공장이다. 
  • 애플리케이션 전체에서 공유하도록 설게되어 있으므로 단 한 개만 생성한다. 
  • EntityManager는 EntityManagerFactory를 이용하여 생성하며 이를 생성하는 비용은 거의 들지 않는다. 
  • EntityManagerFactory 는 여러 스레드가 동시에 접근해도 안전하므로 다른 스레드간 공유할 수 있다.
  • 반면, EntityManager 는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간 공유를 하면 안된다. 

JPA는 다음과 같은 구조로 동작한다.

1. EntityManager Factory를 통해 EntityManager를 자체적으로 생성

2. EntityManager는 내부적으로 DB connection Pool을 이용하여 DB와 커넥션

 

EntityManager는 데이터베이스 커넥션을 바로 사용하는 것이 아니다. 꼭 데이터베이스와 연결이 필요한 시점에 사용한다.


영속성 컨텍스트란?

- 엔티티를 영구 저장하는 환경이라는 뜻이다. 

- EntityManager로 Entity를 저장하거나 조회하면 EntityManager는 영속성 컨텍스트에 Entity를 보관하고 관리한다. 

em.persist(member);

다음과 같이 입력한다는 것은 정확히 'EntityManager를 사용하여 member 를 영속성 컨텍스트에 저장'한다는 의미이다.

 

영속성 컨텍스트는 EntityManager 를 생성할 때 하나 만들어지며 엔티티 매니저를 통해 영속성 컨텍스트에 접근할 수 있고 관리할 수 있다. 


Entity의 생명주기

  • 비영속(new / transiemt) : 영속성 컨텍스트와 전혀 관계가 없는 상태
  • 영속(managed) : 영속성 컨텍스트에 저장된 상태
  • 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제(removed) : 삭제된 상태

// 비영속 상태 = 단순 객체 생성
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

EntityManager em = emg.createEntityManager();
em.getTransaction().begin();

// 영속 상태가 된다.
em.persist(member);

// 준영속 상태로 만들기 = 회원 엔티티를 영속성 컨텍스트에서 분리
em.detach(member);

// 삭제
em.remove(member);

영속성 컨텍스트의 이점은 뭘까?

그렇다면 영속성 컨텍스트의 이점은 무엇일까?

  • 1차 캐시
  • 동일성 보장
  • 트랜잭션을 지원하는 쓰기 지연
  • 변경 감지
  • 지연 로딩

1차 캐시

우선 1차 캐시에 대해서 알아보자. 

JPA 는 em.persist(member); 를 실행하면 위 그림처럼 1차 캐시에 엔티티를 저장한다. 즉, 아직 데이터베이스엔 저장된 상태가 아니란 이야기다. 

 

그 뒤 다음 코드를 실행하여 엔티티를 조회해보자. 

Member member = em.find(Memeber,class, "member1");

위의 코드를 실행하면 JPA 는 우선 1차 캐시에서 Entity를 찾고 만약 찾는 Entity가 1차 캐시에 없으면 데이터베이스를 조회한다. 

 

따라서 데이터베이스에 바로 접근하는 것이 아닌 1차 캐시에서 접근하므로 성능상 이점을 누릴 수 있다.

 

동일성 보장

다음 코드를 보자. 

      Member findMember1 = em.find(Member.class, 101L);
      Member findMember2 = em.find(Member.class, 101L);

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

결과는 true 가 나온다. 

같은 트랜잭션 내에서 조회했을 때 동일성을 보장한다는 이점이 있다. 

 

트랜잭션을 지원하는 쓰기 지연

EntityManager는 트랜잭션을 commit 하기 직전까지 내부 쿼리 저장소에 INSERT SQL 을 모아둔다.

그리고 트랜잭션을 commit 할 때 모아둔 쿼리를 데이터베이스에 보내는데 이것을 트랜잭션을 지원하는 쓰기 지연(transactional write-behind) 이라 한다. 

 

entityManager 가 트랜잭션을 commit 하는 시점에 (flush()) 데이터베이스에 INSERT 쿼리를 날리게 된다. 

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();

//엔티티 매니저는 데이터 변경시 트랜잭션을 시작
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);

//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit();

 

 

변경 감지(dirty checking)

// 영속 엔티티 조회
Member memberA = em.find(Member.class, 101L);
// 영속 엔티티 데이터 수정
memberA.setName("hi");
tx.commit();

위의 코드를 실행하면 따로 update 를 하지 않아도 자동으로 update query 를 실행한다. 다음과 같은 동작 순서에 의해 발생한다. 

 

1. flush() 가 발생하면

2. Entity와 1차캐시의 스냅샷을 비교한다. 

3. UPDATE SQL 생성

4. flush()  -> 영속성 컨텍스트에 있는 값을 데이터베이스로 전송

5. commit()

 

변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 Entity 에만 적용된다. 


출처 

- 자바 ORM 표준 JPA 프로그래밍 (김영한님)

- 자바 ORM 표준 JPA 프로그래밍 - 기본편 (인프런 - 김영한님)

 

 

 

댓글