JPA

[ Jpa ] 영속성 컨텍스트

인생은단짠단짠 2022. 12. 8. 15:37

 

Persistance Context

 

한국어로는 영속성 컨텍스트라고 한다.

 

context란, 프레임워크에서 주로 컨테이너들이 관리하고 있는 내용을 context라고 한다.

persistance란 사라지지 않고, 지속적으로 접근할 수 있다는 의미이다.

 

보통 메모리에 존재하는 데이터는 서비스가 종료되면 사라진다. 그런 데이터를 사라지게 하지 않고, 지속적으로 처리하는 방법은 파일로 저장하거나, DB에 저장하는 것이다. 이렇게 데이터를 영속화하는데 사용되는 컨테이너를 영속성 컨텍스트라고 한다.

 

좀 더 풀어서 말하면 영속성 컨텍스트는 JPA 컨테이너 안에서 동작하는 entity의 맥락을 관리하는 것 이다.

 

원래는 persistence.xml파일을 로딩하여 영속성 컨텍스트 설정을 할 수 있다. 이를 전혀 신경쓰지 않고도 jpa를 활용할 수 있었던 이유는 build.gradlespring-boot-starter-jpa 의존성를 추가했기 때문에 스프링 부트가 자연적으로 영속성 컨텍스트에 대한 설정들을 추가해주었기 때문이다.

 

 

영속성 컨텍스트의 가장 주체적인 역할을 하는 것은 EntitiyManager이다. 

EntitiyManager는 entity 캐시를 가지고 있다. 

 

 


 

1차 캐시

 

1차 캐시는 맵의 형태로 만들어진다.

key는 id값, value는 해당 entity가 들어있다.

 

 

1차 캐시를 통한 조회 프로세스

 

 id로 조회하게 되면

 

1. 영속성 컨텍스트 내에 존재하는 1차 캐시에 entity가 있는지 확인한다.

2. 있으면 DB 조회 없이 캐시에서 바로 entity를 조회 하여 반환한다.

3. 없으면 실제 쿼리로 DB에 조회한 후 그를 1차 캐시에 저장(영속상태로 만들고)하고 반환한다. 

 

 

id말고 다른 값으로 조회하면 캐시가 사용되지 않는다. key값이 id값이기 때문!

실제 아래 테스트에서 email로 조회했을때는 select문이 3번 실행되지만,  id로 조회했을 때는 처음 한번만 select문이 실행된다. 1차 캐시가 동작함에 따라 jpa의 기본성능이 올라가는 것이다.

@Test
void cacheFindTest() {
    System.out.println(memberRepository.findByEmail("grace1@naver.com"));
    System.out.println(memberRepository.findByEmail("grace1@naver.com"));
    System.out.println(memberRepository.findByEmail("grace1@naver.com"));

    System.out.println(memberRepository.findById(1L).get());
    System.out.println(memberRepository.findById(1L).get());
    System.out.println(memberRepository.findById(1L).get());
}

 

 

1차 캐시의 활용

  • 업데이트나 delete할때도 id 값으로 조회하는 select쿼리가 한번 실행된다.
  • 그래서 실제 개발자가 id값으로 직접 조회하는 경우가 많이 없더라도 jpa내부에서 id값 조회가 빈번하게 일어나기 때문에 하나의 트랜잭션 안에서 동작할때에는 1차 캐시를 사용하므로써 성능 저하를 방지할 수 있다.

 

 


Flush

 

실제로 save 메서드를 실행시키는 시점에 db에 반영되지 않는다.

우리가 사용하는 영속성 컨텍스트와 실제 db사이에 데이터 gap이 발생한다는 것이다.

 

 

flush메서드

  • 영속성 컨텍스트에 쌓여있는 데이터는 entity manager가 자체적으로 DB에 영속화를 해주지만, 개발자가 의도한 타이밍에 영속화가 일어나지 않는다. 개발자가 원하는 타이밍에 영속화를 시켜주려면 flush메서드를 사용해야 한다.
  • 영속성 컨텍스트에 쌓여서 아직 반영되지 않는 엔티티의 변경을 해당 메서드 실행시점에 모두 DB에 반영하는 역할을 한다.
  • flush남발하게 되면 영속성 컨텍스트의 장점을 무효화 시키는게 되니 적당히 사용하는게 중요하다.
  • 그리고 flush로 인해 예상치 못한 동작들이 생길 수 있음..

 

 

영속성 캐시가 flush되어서 실제 영속성 컨텍스트와 db가 동기화 되는 시점은 언제일까?

  1. flush메서드가 호출되는 경우 (개발자가 의도적으로 영속성 캐시를 db에 반영)
  2. 트랜잭션이 종료될 경우
  3. id값이 아닌 jpql 쿼리가 실행되는 경우
    1. 데이터를 조회할때 영속성 컨텍스트와 실제 DB 데이터를 비교해서 merge하는 과정이 구현되어야 한다. 이는 실제 영속성 컨텍스트에 있던 값을 모두 flush시키고 다시 DB에서 select하여 가져오게 구현되어있다.

 

 


 

entity 생애주기

 

entity객체 new하면 생기는 상태가 비영속상태이다. entity가 아니라 단순한 자바 object처럼 취급된다.

 

 

 

영속 상태 (managed)

영속상태는 해당 entity가 영속성컨텍스트 관리하에 존재하는 상태이다.  영속상태의 객체는 객체의 변화를 별도로 처리해주지 않더라도 DB에 반영된다.

 

 

영속화 방법

  • save(user) 사용해도 영속화된다. (save안에 em.persist 있기 때문!)
  • entityManager.persist(user) 해도 영속화된다. (직접 영속화하는 것)

 

영속성 컨텍스트내에서 관리되는 entity는 setter통해 정보 변경된 경우에, 별도로 save메서드 호출하지 않더라도 트랜잭션 완료되는 시점에 DB 데이터와의 정합성을 맞춰준다. 

좀 더 자세히 설명하자면, 영속성 컨텍스트에서 가지고 있는 entity 객체는 처음 컨텍스트에 로드할때 해당 정보를 스냅샷처럼 복사해서 가지고 있는다. 그리고 변경내용을 DB에 반영해야하는 시점(flush, transaction종료되고 커밋되는 시점, 복잡쿼리 실행되는 시점)에 변경된 내용있다면 db에 반영해주는 것이다.

이를 더티체크라고 한다.

 


 

준영속상태 (detached)

 

  • 원래 영속되었던 객체를 분리해서 영속성 컨텍스트 밖으로 꺼내는 것이다.
  • entityManager.detach(user)
  • detatch하는 방법은 em호출하는 방법 밖에 없음!  (detatch 할 일 없다는걸 보여주는 것이기도 함.. ㅎㅎ)

entityManager.merge(user)를 통해 다시 managed하게 할 수 있다.

(persist, merge나 모두 save로 할 수 있다.)

 

 

clear, close 사용해도 영속성 컨텍스트 밖으로 꺼내는거다. 근데 이게 좀 더 파괴적이다.

clear - 기존에 반영하려고 했던 것들, 예약된 작업까지 모두 drop한다. (예를 들어 앞에 merge작업이 있었다고 하더라도 이 역시 drop된다. )

 

 


 

삭제 (removed)

  • remove는 managed된 상태에서 할 수 있다.
  • remove하면 영속성 컨텍스트와 db에서 삭제되어, 해당 entity는 더이상 사용하지 못하는 상태가된다.
  • emtityManager.remove(user);