영속성 컨택스트(Persistence Context)란?

영속성 컨텍스트란

JPA에서 가장 중요한 것을 두가지 뽑으라면 객체와 관계형 데이터베이스의 매핑과 영속성 컨텍스트를 뽑을 수 있을 것 같다!!

EntityManager는 요청마다 생겨서 database에 대한 connection을 얻은 후 database에 필요한 작업을 한다!!

이때 EntityManager 객체 em에 대해 em.persist()를 하면 database에 insert가 된다고 생각을 하고있으나

정확히는 persist는 디비에 저장하는게 아니라 영속성 컨택스트에 저장하는거야!!

영속성 컨텍스트가 무엇이냐?? 흐응으으음… 눈에 보이지 않는 어떤 논리적인 개념이라고 생각하면 되는데 엔티티 메니저를 통해 이 영속성 컨텍스트에 접근할 수 있다! 정도로만 느낌 잡고 우선 가즈아~!

엔티티가 뭐라고 했쥐?? jpa가 관리하는 놈이라고 생각하면 된다고 했지.

이 엔티티에는 생명주기가 있어.

  • 비영속
  • 영속
  • 준영속
  • 삭제

이렇게 4가지가 존재해.

위 4가지를 흐름에 따라 스윽 이야기해보도록 하겠슴돠!!



비영속

먼저 비영속을 보자!!

비영속은 영속성 컨텍스트와는 전혀 관계가 없는 새로운 상태야!! 그냥 new만 했을 때 그러한 상태겠지??

1
2
3
4
//Example은 Entity
Example ex = new Example();
ex.setId("ex1");
ex.setName("example1");

위에 보이는게 딱 비영속 상태인거야!!


영속

얘가 영속이 되려면?? 그 전에 먼저 영속은 영속성 컨텍스트에 manage 즉 관리되는 상태라고 생각하면 돼!!

1
2
3
4
EntityManger em = emf.createEntityManager();
em.getTransaction().begin();

em.persist(ex);

이렇게 하면 저 ex는 영속상태가 되었고 영속성 컨텍스트의 관리 대상이 되었다고 생각하면 되는거야!!


준영속, 삭제

다음으로 준영속과 삭제는 함께 슥 해치워 버릴게

1
2
3
4
5
//ex 를 영속성 컨테스트에서 분리 -> 준영속 상태
em.detach(ex);

//객체를 삭제한 상태(삭제)
em.remove(ex);



영속성 컨텍스트의 이점

이렇게 영속성 컨텍스트를 이용할 때의 이점은 무엇일까?? 다음과 같애

  • 1차 캐시
  • 동일성(identity) 보장
  • 트랜잭션을 지원하는 쓰기 지연
  • Dirty Checking
  • Lazy Loading


1차 캐시

먼저 1차캐시에 대해서 알아보자!! 영속성 컨테스트를 1차캐시라고 생각해도 No Problem 이야!!

우선 아래 코드를 보자

1
2
3
4
5
6
7
//엔티티를 생성한 상태(비영속)
Sample s = new Sample();
s.setId("s1");
s.setName("샘플1");

//엔티티를 영속 = 1차 캐시에 저장됨
em.persist(s);

자 이렇게 넣고 나면 어떻게 되느냐?? 주석에도 적혀있듯이 1차캐시에 저장되었다고 생각해도 무방해.

1차캐시는 Id를 key, entity를 value로 넣어놓은 해쉬? 테이블? 구조라고 생각하면 쉬울 것 같애

위의 예에서는

s1 key에 s라는 엔티티가 매핑되어 있겠지.

이 상황에서

1
2
//1차 캐시에서 조회
Sample findSample = em.find(Sample.class, "s1");

이런식으로 find를 실행하게 되면 우선적으로 1차캐시에서 s1이라는 id를 통해 entity를 찾게 돼!! 만약 찾자나?? 그럼 db에 쿼리를 날리지 않고 그 놈을 들고오게 되는거지!!

그럼 만약에 find를 하였는데 해당하는 entity가 1차 캐시에 없다면 (영속 상태가 아니라면) 어떻게 하느냐!?

→ database에 쿼리를 날려 해당하는 데이터를 들고와 이 데이터의 key와 객체 정보를 통해 1차캐시에 저장함으로써 영속상태로 만들고 사용자에게 반환해줘.


동일성 보장

이는 위의 내용과 이어진다고봐도 무방해!! 위의 작성한 코드에서 이어나가서 진행해볼게

1
2
3
4
Sample a = em.find(Sample.class, "s1");
Sample b = em.find(Sample.class, "s1");

System.out.println(a == b); //true 반환

이렇게 같은 정보를 통해 가져온 Entity에 대해서는 동일성을 보장해줘!! 영속성 컨텍스트가 관리하는 상태가 영속상태라고 했고 이는 곧 1차캐시에 해당 Entity가 존재한다고 봐도 되는 것이라고 했지.

그런 경우에 1차캐시에서 가져오는 Entity는 당연히 동일한 id로 조회한 결과일 것이고 같은 객체임을 보장받을 수 있게 되는거지!!

→ 1차캐시 덕분에 REPEATABLE READ 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 어플리케이션 차원에서 제공받을 수 이써!!


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

JPA에서 모든 것이 일어나는 단위는 트랜젝션이라고 생각해도 무방해!! 트랜젝션이라는 단위로 동작이 되어지기에 JPA의 여러 기능들이 이용 가능하게 되는거야!!

그리고 TRANSACTION이 커밋되는 순간 쿼리 저장소(실제 persist등의 코드가 실행될 때 바로 쿼리가 날라가는 것이 아니라 쿼리를 생성 후 임시 저장소에 저장 해 놓는데 해당 저장소를 쿼리 저장소라 부르겠다)의 쿼리를 db에 날려주는거야!!

1
2
3
4
5
6
7
8
9
//em : EntityManager, transaction : EntityTransaction
transaction.begin();

em.persist(sample1);
em.persist(sample2);
//여기까지 insert sql을 db에 보내지 않아!!

transaction.commit();
//여기서 이제 디비에 insert sql을 날리는거지!!

위처럼 이렇게 디비에 쓰는작업을 지연시켰다가 트랜젝션이 commit 될 때 스윽 날리는 걸 의미해!!

그럼 commit할 때 어떤일이 일어나길래 db에 쿼리가 날라가느냐??

flush라는 동작을 하게 되는데 이 flush 라는 친구가 1차 캐시에서 관리하는 Entity와 db를 동기화 해주는 작업이라고 보면 될 거 같애.

쉽게 말하면 flush할 때 그냥 쿼리가 날라간다! 라고 생각하면 될 거 같으.


Dirty Checking

dirty checking은 변경감지라고도 해. 예를들어 영속상태의 엔티티를 변경한다면 따로 update하는 작업을 하지 않아도 db에 있는 데이터에 변경된 정보로 동기화 시켜준다는 거지!!

1
2
3
4
5
6
7
Sample s1 = em.find(Sample.class, "s1");

//영속 엔티티 수정
s1.setNmae("new s1");

//em.update(s1) 이런 놈이 필요없다 이말이야!!
transaction.commit();

이렇게 알아서 변경을 감지해서 디비 정보를 업데이트 시켜줘!!

아쥬~ 좋은 편리하다 이말이야~~

위에서 이야기한 것 처럼 변경이 되어질 때 update쿼리를 만들어 놨다가 commit할 때 보내버리는 거겠지.


Flush에 대하여

🧐 잠깐!! 플러시(flush)에 대해 조금 더 자세히 보자!

아까 commit을 하면 fush를 함으로써 쿼리 저장소에 있던 쿼리를 디비에 날려줘서 1차캐시와 디비의 정보를 동기화한다는 내용이 있었는데 조금 더 자세히 알아보자!!

과정은 다음과 같애

  1. 변경을 감지해!!
  2. 수정된 엔티티에 대한 update 문을 쓰기 지연 sql 저장소에 등록해
  3. 쓰기 지연 sql 저장소의 쿼리를 데이터베이스에 전송해!!

위와 같은 과정으로 동작해!!

그럼 영속성 컨텍스트를 flush 하는 데에는 commit하는 방법 뿐일까?? 아니야!! 아래 3가지 방법이 있어!!

  1. em.flush() 직접 호출
  2. 트랜잭션 커밋 (flush 자동호출)
  3. jpql 쿼리 실행 (flush 자동호출)

물론 쿼리를 실행할 때 flush 호출되는 것을 원치 않는다고 한다면 설정을 통해 변경할 수 있어
1
2
em.setFlushMode(FlushMoeType.AUTO); //커밋이나 쿼리 실행할 때 플러시(default)
em.setFlushMode(FlushMoeType.COMMIT); //커밋할 때만 플러시

준영속 이어서

후 그럼 이어서 준영속 상태에 대해 이야기 해 보즈아!!

준영속은 영속 상태의 엔티티가 영속성 컨택스트에서 분리(detached)된 상태야

→ 영속성 컨텍스트가 제공하는 기능을 더는 사용하지 못하겠지

준영속 상태로 만드려면 어떻게 해야할까??

3가지 방법이 있다!!

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

이상!





참조 : https://www.inflearn.com/course/ORM-JPA-Basic