JPA 영속성 컨텍스트(Persistence Context)

1. 영속성 컨텍스트(Persistence Context)

영속성 컨텍스트란 논리적인 개념(무형성)으로 엔티티를 영구 저장하는 환경이라고 할 수 있습니다. 특히, JPA를 이해하는데 가장 중요한 용어입니다. 그리고 EntityManager를 통하여 영속성 컨텍스트에 접근할 수 있습니다.

1
EntityManager.persist(entity);

다음과 같이 EntityManagerFactory가 생성시킨 EntityManager를 사용하여 Connection pool에 접근하여 Database에 접근할 수 있습니다.

2. J2SE (Standard Edition) VS J2EE ( Enterprise Edition)

J2SE

일반 자바 프로그램 개발을 위한 용도로 이용되는 개발도구이며 각종 자료구조, 기본 유틸리티, 스윙이나 AWT와 같은 GUI도구등의 기본기능을 포함하고 있다.

J2EE

엔터프라이즈 환경을 위한 도구로 EJB, JSP, Servlet, JNDI같은 기능을 지원하며 웹 애플리케이션 서버를 이용하는 프로그램 개발시 많이 사용한다.

두 가지 환경에서는 어떠한 차이가 있을까요?

J2SE는 엔티티매니저와 영속성컨텍스트가 1:1로 관계를 가지고 있으며 J2EE 스프링 프레임워크 같은 컨테이너 환경은 엔티티 매니저와 영속성 컨텍스트가 N:1의 관계를 가지고 있습니다.

3. Entity 생명주기

생명주기

엔티티 생명주기

3.1. 비영속(new/transient)

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

1
2
3
4
// 객체 생성 (비영속)
Member member = new Member();
member.setId("memberId1");
member.setUsername("gwanhyeonkim")

3.2. 영속(managed)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
// 객체 생성(비영속)
Member member = new Member();
member.setId("memberId1");
member.setUsername("gwanhyeonkim")

// 엔티티 매니저 팩토리로 엔티티 매니저를 생성합니다.
EntityManager em = emf.createEntityManager();
// 트랙잭션위에서 동작합니다.
em.getTransaction().begin();

// 객체를 저장한 상태(영속) - 영속상태가 되는것 DB에 저장되는 상태가 아니며 트랜잭션 커밋시점에 해당 DB에 들어가게 됩니다.
// 만약 1차캐시가 있다면 1차캐시를 사용합니다.
em.persist(member);

3.3. 준영속(detached)

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

1
2
// 엔티티를 영속성 컨텍스트에서 분리시키고 준영속상태로 만듭니다.
em.detach(member);

3.4. 삭제(removed)

삭제된 상태

1
2
// 객체를 삭제한 상태
em.remove(member);

4. 영속성 컨텍스트 특징

  1. 1차캐시를 활용합니다.
  2. 동일성(Identity)를 보장합니다.
  3. 트랜잭션을 지원하는 쓰기 지연을 일으킵니다(Transcational write-behind)
  4. 변경 감지(Dirty Checking)이 가능합니다.
  5. 지연 로딩(Lazy Loading)을 지원합니다

즉, 영속성 컨텍스트는 버퍼링과 캐싱의 기능을 가질 수 있습니다.

5. 엔티티 조회 및 1차 캐시

1차 캐시는 Map형태로 구성되어있습니다. 예를 들면 key-@Id : value-@Entity <Key,Value>형식으로 구성되어있습니다.

1
2
3
4
5
6
7
//엔티티를 생성한 상태 비영속상태입니다.
Member member = new Member();
member.setId("memberId1");
member.setUsername("gwanhyeonkim")

//엔티티를 영속하는 단계
em.persist(member);

이제 이것들은 1차캐시에서 어떻게 사용될까요?

1
2
3
4
5
6
7
8
9
10
//엔티티를 생성한 상태 비영속상태입니다.
Member member = new Member();
member.setId("memberId1");
member.setUsername("gwanhyeonkim")

// 1차 캐시에 저장
em.persist(member);

// 1차 캐시에서 조회
Member member1 = em.find(Member.class, "gwanhyeonkim");

em.persist(member)를 하게 되면 1차 캐시에 값이 저장되고 그 이후에 만약 값을 조회하는 경우 현재 1차캐시에 올라간 값으로 조회해옵니다.
즉, 영속성 컨텍스트에서 1차캐시를 확인하고 없으면 1차캐시에 저장을 하게됩니다. 그리고 값을 조회하면 1차캐시에 저장된 저장값들을 불러와서 조회가 가능하게 됩니다.

데이터베이스 조회

EntityManager은 트랜잭션단위로 처리를 진행합니다. 즉, 1차 캐시도 날아가며 1차캐시는 전체적인 확인을 통해 진행됩니다.

  1. find(“gwanhyeonkim”) 1차 캐시를 확인하여 없으면 DB를 조회합니다.
  2. DB조회후 1차캐시에 저장을 시키고 해당 member값을 반환시켜주게 됩니다.

6. 영속성 엔티티 동일성(Identifier)

만약에 똑같은 값을 조회한다고 가정하면 영속성 엔티티의 동일성을 보장시켜줍니다.

1
2
3
4
Member member1 = em.find(Member.class, "gwanhyeonkim");
Member member2 = em.find(Member.class, "gwanhyeonkim");

(a == b) // 동일성이 같습니다. true

1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공됩니다. 즉, 같은 트랜잭션내에 동일성이 보장되면 true를 리턴한다고 할 수 있습니다.

데이터베이스에 언제 Insert를 진행할까요?

1
transaction.commit(); // 해당 시점에 트랜잭션 커밋을 진행하면서 insert값들을 실제 SQL쿼리를 날리게 됩니다. 즉, 쓰기지연이 일어나는 시점이며 flush SQL이 같이 동작됩니다.

em.persist()

persist가 동작하면 INSERT SQL과 1차캐시에 저장을 동시에 진행합니다. INSERT SQL생성시 쓰기 지연 SQL저장소에 저장시키고 1차 캐시에서 해당 캐시가 있는지 없는지를 확인하고 있으면 해당 값을 캐시에서 가져오고
그게 아니라면 영속성컨텍스트가 DB에 접근하여 SQL 쓰기지연저장소에 저장된 쿼리를 날리게 됩니다. transaction.commit()으로 쓰기지연이 동작하고 flush sql이 함께 동작됩니다. 이때 해당 실제 DB에 값을 넣게 됩니다.

이것을 쓰는 이유는 무엇일까요? 버퍼링, 즉, 캐싱개념이 생기게 됩니다.

7. 엔티티 수정

영속성 엔티티를 조회후에 데이터를 수정한다면 어떻게 처리가 될까요?

1
2
3
4
5
6
7
8
9

//영속 엔티티 조회
Member member1 = em.find(Member.class, "gwanhyeonkim");

//영속 엔티티 데이터 수정
member1.setUsername("kgh");
member1.setAge("20");

transcation.commit(); // 트랜잭션을 커밋하는 시점입니다.

데이터수정시 em.persist를 넣을 필요는 없게 됩니다. 이미 영속성컨텍스트에 관리되어지고 있기 때문입니다.

8. 변경 감지(Dirty Checking)

변경 감지 순서에 대해서 알아보겠습니다.

영속성 컨텍스트(entity manager)안에서 transcation.commit()이 작동되게 되면 어떻게 처리될까요?

  1. flush()함수가 동작됩니다.
  2. 엔티티 스냅샷(최초 시점에만)과 비교되어 집니다.
  3. 쓰기지연 저장소에 UPDATE SQL이 생성됩니다.
  4. 다시 flush()를 수행하면서 SQL UPDATE구문이 DB에 날리게 되면서 이때, commit이 진행됩니다.

9. 엔티티 삭제

1
2
Member member1 = em.find(Member.class, "gwanhyeonkim");
em.remove(member1);

삭제 대상 엔티티를 조회하고 엔티티를 삭제하는 구문입니다.

10. 플러시(Flush)

영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는것을 뜻합니다. 즉, 지연 sql 스냅샷과 쓰기지연 SQL 저장소에 저장해놓은 쿼리를 데이터베이스에 반영하는 과정이라고 생각하시면 됩니다.

  1. 변경 감지(dirty checking)를 진행합니다.
  2. 수정된 엔티티 쓰기 지연 SQL 저장소에 등록됩니다.
  3. 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송합니다. (등록, 수정, 삭제)

11. 영속성 컨텍스트를 플러시를 어떻게 할까요?

  1. em.flush()를 직접호출하여 진행합니다. 테스트를 진행할때 이러한 방식으로 새로가동하는것처럼 진행할 수 있습니다.
  2. 트랜잭션 커밋을 진행합니다. 이때 플러시가 자동으로 같이 호출되게 됩니다.
  3. JPQL쿼리를 실행합니다. 이때 플러시가 자동으로 같이 호출되게 됩니다. JPQL쿼리실행시 1차 캐시가 사라지는것이 아니고 쓰기지연SQL, 변경감지 업데이트 쿼리들이 DB에 반영됩니다.

12. JPQL 쿼리 실행시 플러시가 자동으로 호출되는 이유

em.persist에 persist를 실행한다고 하였을때 persist 자체만으로 DB에 접근은 하지 못합니다. 그 이유는 JPQL을 날리게 되면 자동으로 flush()를 호출하여 DB를 가져오기때문에 현재 값들을 select해올 값이 없게 됩니다.

13. 플러시 모드 옵션

  1. FlushModeType.AUTO - 커밋이나 쿼리를 실행할 때 Flush(Default)
  2. FlushModeType.COMMIT - 커밋할때만 Flush
1
em.setFlushMode(FlushModeType.COMMIT)

14. 플러시 특징

  1. 영속성 컨텍스트를 비우지 않습니다.
  2. 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화합니다.
  3. 트랜잭션이라는 작업단위가 중요하며 커밋직전에만 동기화를 진행하면 됩니다.

15. 준영속 상태

  1. 영속상태(1차캐시에 저장된 상태)에서 준영속상태를 만드는 상태
  2. 영속상태의 엔티티가 영속성 컨텍스트에서 분리합니다.(detached) - Dirty Checking을 사용하지 못합니다.
  3. 영속성 컨텍스트가 제공하는 기능을 사용하지 못합니다.

준영속 상태로 어떻게 만들 수 있을까?

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