Application/JPA

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

반응형
JPA에 대해서 헷갈렸던 개념들을 위주로 정리하는 글입니다.

자바 ORM 표준 JPA 프로그래밍

JPA 내부 동작 원리를 알려면 영속성 컨텍스트를 알아야 함.

EntityManager

EntityManagerFactory는 여러 스레드에서 동시에 접근해도 안전하지만, 생성하는 비용이 상당히 크다.

따라서 EntityManagerFactory에서 요청이 올 때마다 생성 비용이 거의 없는 EntityManager를 생성한다. (EntityManager는 Thread Safe하지 않아, 여러 스레드가 동시에 접근하면 동시성 문제가 발생한다 = 요청(스레드)별로 한 개 씩 할당)

이때 만들어진 EntityManager는 내부적으로 Database Connnection을 사용해서 DB를 사용한다.

(참고로 EntityManager는 데이터베이스 연결이 꼭 필요한 시점까지 커넥션을 얻지 않는다.)


영속성 컨텍스트란?

영속성 컨텍스트

- 엔티티를 영구 저장하는 환경

- 영속성 컨텍스트는 애플리케이션과 데이터베이스 사이에서 객체를 보관하는 논리적 개념

- EntityManager를 통해서 영속성 컨텍스트에 접근. (EntityManager가 생성되면 논리적 개념인 영속성 컨텍스트(PersistenceContext)가 1:1로 생성된다)


엔티티의 생명 주기

엔티티의 생명 주기

- 비영속

영속성 컨텍스트와 전혀 관계가 없는 새로운 상태

Member member = new Member("강승호");

- 영속

영속성 컨텍스트에 관리되는 상태

Member member = new Member("강승호");

EntityMangager entityManager = entityManagerFactory.createEntityManager();
entityManager.persist(member); // 이제 영속성 컨텍스트에서 관리한다.

주의사항) EntityManager.persist(member); 를 한다고 DB에 저장되는 것은 아니고, 커밋 혹은 flush() 이후에 DB에 저장된다. 그전까지는 영속성 컨텍스트에만 존재.

 

 

- 준영속

영속성 컨텍스트에 저장되었다가 분리된 상태

Member member = new Member("강승호");

EntityMangager entityManager = entityManagerFactory.createEntityManager();
entityManager.detach(member);

개발자가 직접 준영속을 만들 일은 거의 없음

 

- 삭제

Member member = new Member("강승호");

EntityMangager entityManager = entityManagerFactory.createEntityManager();
entityManager.remove(member);

실제 DB에서 삭제된 상태

 


영속성 컨텍스의 식별자 값

- 영속성 컨텍스트는 엔티티를 식별자 값(@Id로 테이블의 기본 키와 매핑한 값)으로 구분한다.
따라서 영속 상태는 식별자 값이 반드시 있어야 한다.


그러면 애플리케이션과 데이터베이스 사이의 논리적인 영속성 컨텍스트가 있는 이유? 있으므로 좋은 점?

1. 1차 캐시

1차 캐시

entityManager.persist(member); // 를 하면 영속성 컨텍스트의 1차 캐시에 저장 된다.

Member findMember = entityManager.find(Member.class, 1L); // 1차 캐시에서 조회

만약에 1차 캐시에 없을 경우, DB에서 직접 조회하고 1차 캐시에 저장 후 반환.

요청이 시작되면 영속성 컨텍스트를 생성하고, 끝나면 영속성 컨텍스트를 지움. 이때 1차 캐시도 삭제된다.

따라서 애플리케이션 전체에서 공유하는 것이 아님 (애플리케이션 전체에서 공유하는 캐시는 2차 캐시)

 

2. 동일성 보장 (identify)

Member a = entityManager.find(Member.class, 100L);
Member b = entityManager.find(Member.class, 100L);

a == b // true

entityManager.find(Member.class, 100L); 을 반복해서 호출해도 영속성 컨텍스트는 1차 캐시에 있는 같은 인스턴스를 반환한다.

이러한 이유로 영속 엔티티의 동일성 즉 == 비교를 보장할 수 있다.

 

1차 캐시로 반복 가능한 읽기 (REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공.

 

 

3. 트랜잭션을 지원하는 쓰기 지연 (Transaction Write-behind)

em.persist(member1);
em.persist(member2);

em.persist(member1);
em.persist(member2);

이때 바로 INSERT 쿼리를 데이터베이스에 보내지 않고. 1차 캐시에 저장하면서 동시에 쓰기 지연 SQL 저장소에 쌓아 둔다.

transaction.commit();

transaction.commit(); 

커밋되는 순간 데이터베이스에 INSERT 쿼리를 보냄. (참고로 flush() 호출 시 커밋과 별도로 저장될 수 있음)

 

4. 변경 감지 (Dirty Checking)

Member member = entityManager.find(Member.class, 10L);
member.setName("변경 후");

transaction.commit(); // Update 쿼리가 나감... 왜?

JPA는 변경 감지라는 기능으로 엔티티를 변경할 수 있는 기능을 제공한다.

 

동작 원리)

 

영속성 컨텍스트의 변경 감지 

JPA는 트랜잭션 되는 순간 내부적으로 flush()가 호출된다.
그때 엔티티와 1차 캐시 내의 스냅샷(최초 상태)을 비교한다.

비교했을 때, 변경이 있을 때, UPDATE SQL를 쓰기 지연 SQL 저장소에 저장.

커밋이 되면 flush()가 호출돼서 DB에 UPDATE 쿼리가 나감.

 

 

5. 지연 로딩 (Lazy Loading)

연관 관계 매핑되어 있는 엔티티 조회 시 프록시를 반환함으로써 쿼리를 진짜 필요할 때 날리는 기능

 


플러시란?

영속성 컨텍스트의 변경 내용을 데이터베이스에 반영

 

플러시가 발생하면

- 변경 감지

- 수정된 엔티티 쓰기 지연 SQL 저장소에 등록

- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)

 

영속성 컨텍스트를 플러시하는 방법

- em.flush()

- 트랜잭션 커밋

- JPQL 쿼리 실행

 

플러시 주의 사항

- 영속성 컨텍스트를 비우지 않음

- 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화

반응형