DB/JPA

[JPA] PersistenceContext 간단 정리

구데타마 2021. 8. 20. 22:58

 

개요

이번 게시글에서는 JPA 내부 구조 및 동작 방식을  파악하는데 중요한 개념인 PersistenceContext(영속성 컨텍스트)에 대해 알아보겠습니다.

 

1. EntityManagerFactory & EntityManager

JPA 내부 구조를 살펴보면 EntityMangerFactory 객체와 EntityManager 객체가 있습니다.

EntityManagerFactor객체는 고객의 요청이 올 때마다 즉, 각 쓰레드마다 EntityManager 객체를 생성하고 해당 객체는 내부적으로 DB Connection Pool을 활용하여 DB에 연결을 합니다.

위 내용을 코드로 구성해보면 아래와 같습니다.

 

 

 

2. PersistenceContext (영속성 컨텍스트)

  • JPA를 이해하는데 가장 중요한 용어
  • 물리적인 개념이 아니며 논리적인 개념
  • 앞서 소개한 EntityManager를 통해 PersistenceContext에 접근
    • EntityManager의 persist 메서드를 통해 엔티티를 저장
    • 각 쓰레드마다 EntityManagerFactory에서 EntityManager 객체를 생성하므로 EntityManager와 PersistenceContext는 N:1 관계

 

2.1 엔티티의 생명주기

PersistenceContext를 이해하기 위해서는 엔티티의 생명주기에 대해서 알아야 합니다.

생명주기 내 엔티티의 상태는 아래와 같이 4 가지로 구분할 수 있습니다.

  • new/transiet(비영속): PersistenceContext와 전혀 관계가 없는 새로 생긴 상태
  • managed(영속, persist): PersistenceContext에 관리되는 상태
  • detached(준영속): PersistenceContext에 저장되었다가 분리된 상태
  • removed(삭제): 삭제된 상태

 

 

2.1.1 New/Transient (비영속)

  • 비영속 상태는 아래와 같이 객체를 생성한 상태이며 아직 EntityManager에 의해 관리되지 않은 상태

 

// 비영속 (객체를 생성한 상태)
Blogger blogger = Blogger.builder()
    .id("jaimemin")
    .blogUrl("jaimemin.tistory.com")
    .build();

 

2.1.2 Managed (영속)

  • 영속 상태는 EntityManager에 의해 객체가 관리되고 있는 상태
  • 단, 영속 상태라고 해서 DML이 바로 DB에 날아가는 것은 아님
  • 트랜잭션을 commit 해야지 쿼리가 날아감

 

EntityManagerFactory entityManagerFactory
                = Persistence.createEntityManagerFactory("[PERSISTENCE UNIT]");
EntityManager entityManager = entityManagerFactory.createEntityManager();

// 객체를 저장한 상태 (영속)
entityManager.persist(Blogger.builder()
    .id("jaimemin")
    .blogUrl("jaimemin.tistory.com")
    .build());

 

2.1.3 Detached (준영속)

  • 준영속 상태는 EntityManger에서 분리되어 있는 상태
  • EntityManager가 제공하는 기능을 사용 못함

 

// 엔티티를 EntityManager에서 분리
entityManager.detach(blogger);

 

2.1.4 Removed (삭제)

  • 객체를 삭제한 상태

 

entityManager.remove(blogger);

 

2.2 PersistenceContext를 사용함에 따른 장점

  • 1차 캐시 기능
  • Java Collection과 같이 객체 동일성 보장
  • 트랜잭션을 지원하는 쓰기 지연 지원
  • Dirty Checking 지원
  • Lazy Loading 지원

 

2.2.1 1차 캐시 기능

  • New/Transient 상태에서 Managed 상태가 될 때 EntityManager 내 1차 캐시에 해당 객체를 저장
  • 캐시는 Map과 같은 형태이며 id를 key, 객체를 value 값으로 가지는 형태
  • 객체를 조회할 때, EntityManager는 우선 캐시를 조회한 후 해당 객체가 캐시에 있을 경우 DB를 거치지 않고 바로 캐시에서 해당 객체를 꺼내서 반환해줌 (속도 향상)
  • 만약 데이터베이스에 없을 경우 DB에서 조회한 후 1차 캐시에 객체를 저장한 뒤 해당 객체를 반환하는 프로세스를 거침
  • 위와 같은 과정을 보면 성능에 큰 도움을 줄 것 같지만 1차 캐시는 Transaction 단위로만 유지가 됨
    • 고객의 요청이 끝날 경우 1차 캐시를 flush 하기 때문에 성능 이점을 누리기 힘든 구조
    • 하지만, 비즈니스 로직이 엄청 복잡한 서비스라면 1차 캐시 기능을 통해 성능을 많이 향상할 수 있음

 

2.2.2 Java Collection과 같이 객체 동일성 보장

  • 영속 엔티티는 Java Collection과 유사하게 동작
  • 아래와 같이 같은 트랜잭션 내 동일한 쿼리로 조회한 객체는 동일성 유지

 

Blogger jaimemin = entityManager.find(Blogger.class, "jaimemin");
Blogger jaimemin2 = entityManager.find(Blogger.class, "jaimemin");

log.info(jaimemin == jaimemin2); // true

 

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

  • 앞서 생명주기에서 영속 상태라고 해서 바로 DB에 DML 쿼리가 날아가는 것이 아니라고 언급했음
  • EntityManger의 persist 메서드를 호출할 때마다 DB에 쿼리를 날릴 경우 네트워크 오버헤드가 커질 수 있음
  • 따라서, EntityManager는 persist 메서드를 호출할 때마다 SQL 저장소에 쿼리를 쌓아놓은 뒤 트랜잭션을 commit 할 때 쿼리를 한 번에 날림
  • transaction.commit() 메서드가 호출되면 내부적으로 아래와 같은 과정을 거침
    • commit()이 호출되고
    • flush 메서드가 호출되며 JDBC Batch를 통해 SQL 저장소에 쌓인 쿼리들이 DB에 날아가고
    • 최종적으로 DB에 반영되도록 commit() 메서드가 호출됨

 

* 객체를 삭제하는 remove 메서드 또한 트랜잭션이 커밋되는 시점에 반영이 됨

 

 

 

2.2.4 Dirty Checking 지원

  • EntityManager에 의해 관리되는 객체는 변경이 자동적으로 감지됨
  • 이 말은 즉슨 엔티티를 수정한 뒤에 트랜잭션을 커밋할 경우 굳이 update 쿼리를 직접 호출하지 않더라도 Dirty Checking이 지원되기 때문에 자동으로 호출이 됨
  • transaction.commit() 메서드가 호출된 뒤 내부적으로 아래와 같은 과정을 거침
    • flush() 메서드가 호출되고
    • 엔티티와 기존 스냅샷에 저장된 엔티티를 비교
    • 비교했을 때 변경된 내용이 있을 경우 UPDATE 쿼리 생성한 뒤 SQL 저장소에 저장
    • flush 메서드가 호출되며 JDBC Batch를 통해 SQL 저장소에 쌓인 UPDATE 쿼리가 DB에 날아가고
    • 최종적으로 DB에 반영되도록 commit() 메서드가 호출됨

 

* SNAPSHOT: 최초로 Entitymanager에 들어온 시점의 객체 상태

 

 

 

2.2.5 Lazy Loading 지원

아래와 같이 객체 내 필드로 또 다른 객체가 있다고 가정

 

Class Blogger {
	
    private String id;
    
    private String blogUrl;
    
    private IpPort ipPort;
}
  • 기존대로라면 불러올 때 Blogger 내 모든 필드를 불러옴
  • Lazy Loading이 지원되면 id와 blogUrl 필드가 먼저 불러오고 IpPort 객체에 대한 정보가 필요한 시점에서 IpPort 객체 정보를 불러옴
  • 필드가 많을 경우 Lazy Loading 덕분에 성능 최적화

 

3. Flush 메서드

  • 앞서 설명한 엔티티의 생명주기와 PersistenceContext 장점에 flush 메서드가 언급됐었음
  • flush 메서드는 EntityManager의 변경 내용을 DB에 반영하는 메서드
  • flush는 아래와 같은 상황에서 발생
    • 변경 감지 (Dirty Checking)
    • 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송 (INSERT, UPDATE, DELETE 쿼리)

 

3.1 EntityManager를 flush 하는 방법

  • flush() 메서드를 직접 호출
    • transaction을 커밋하기 전에 DB에 반영되고 싶을 때 해당 방법을 사용하지만 이렇게 사용할 일 거의 없음
    • flush 메서드가 호출되더라도 트랜잭션이 끝난 것이 아니기 때문에 1차 캐시는 유지가 되며 EntityManager의 변경내용을 DB와 동기화
    • 결국에는 트랜잭션 작업 단위 내에서 실행되면 되므로 commit 직전에만 flush를 하면 됨
  • transaction.commit(): flush 자동 호출
  • JPQL 쿼리 실행: flush 자동 호출

 

3.2 flush 모드 옵션

  • FlushModeType.AUTO (디폴트 값): commit이나 쿼리 실행 시 flush
  • FlushModeType.COMMIT: commit 할 때만 flush

 

entityManager.setFlushMode(FlushModeType.COMMIT)

 

출처

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

반응형

'DB > JPA' 카테고리의 다른 글

[JPA] 상속관계 매핑  (2) 2021.09.07
[JPA] 다양한 연관관계 매핑  (0) 2021.08.31
[JPA] 연관관계 매핑 간단 정리  (0) 2021.08.28
[JPA] Entity 매핑 정리  (0) 2021.08.23
JPA를 학습해야하는 이유  (1) 2021.08.16