JPA 영속성 컨텍스트

김동욱·2023년 2월 1일
0

예전에 개발을 공부보단 취미로 하던 시절 데이터를 DB에 입력하는 행위를 persist라고 표현한다는 것에 매우 의아해 했던 기억이 있다. 데이터를 입력하는 명령은 그 의미상으로 봤을 때 insert나 create가 더 가깝기 때문이다. 실제로 node.js로 개발을 하면서 insert나 create를 쓰거나 본 적은 있어도 persist에서는 본 적이 없었기 때문이다.
이번에 자바 Spring Boot를 공부하면서 JPA에 대해 공부를 하면서 이 persist에 대한 의미를 알게 되었다.

EntityManager와 영속성 컨텍스트

EntityManager과 EntityManagerFatory라는 개념이 있다. 사용자가 서버로 요청을 보내면 EntityManagerFactory에서 EntityManager을 만들어낸다. 이 EntityManager가 하는 일이 바로 영속성 컨텍스트를 생성하고 관리하는 일을 한다. 예시를 들자면

  1. User가 Server로 DB에서 Diary에 대한 정보를 가져오는 요청을 했다.
  2. DB에 접근할 때, EntityManagerFactory에서 EntityManager를 생성한다.
  3. 이 EntityManager는 DB에서 Diary에 대한 정보(Entity)를 가져온다.
    이때 영속성 컨텍스트라는 곳에 이 Entity가 남는다.
  4. 이외에 Create Update, Delete에 대한 요청은 모두 다 영속성 컨텍스트를 대상으로 이루어진다.

맞다. 우리들이 DB를 수정할 목적으로 작성한 명령어들이 사실은 DBMS로 바로 이동하는 것이 아닌 영속성 컨텍스트를 거치는 거나 이동했다가 거치는 것이다.

그럼 궁금한 점이 두 가지가 생긴다. 도대체 왜 이 기능이 있는 것이며 언제 DB에 query가 전달되는 것인지가 바로 그것이다.

영속성 컨텍스트가 주는 효과

1차 캐시

예시 시나리오로 설명하자면
User가 보낸 요청에 Diary를 DB에서 가져오는 로직이 두 개가 작성되었다고 하자.

Diary diary1 = find(Diary.class, "1");
Diary diary2 = find(Diary.class, "1");

이 경우 DB로 요청이 두번 갈 것 같지만 사실은 아니다. 왜냐하면 (1) 첫번째 라인에서 DB로 요청을 보내 Entity를 가져와 영속성 컨텍스트에 담고 (2) 두번째 라인에서는 영속성 컨텍스트에서 Entity를 가져오기 때문이다.
이 경우 우리는 영속성 컨텍스트가 캐시로서 사용되었음을 알 수 있다.

Entity의 동일성 보장

Diary diary1 = find(Diary.class, "1");
Diary diary2 = find(Diary.class, "1");
boolean result = diary1 == diary2

result의 경과는 무엇일까? 바로 true다. 앞서 설명한 내용을 이해했으면 당연한 이야기이다. diary1이나 diary2나 결국 영속성 컨텍스트에 있는 같은 Entity를 받은 것이니까.

쓰기 지연

여기서 우리가 앞서 궁금해 했던 언제 DB로 query가 전달되는 것인가에 대한 답변이 있다. 코드 상에 구현된 DB에 대한 요청은 그 줄에서 바로 DB로 전달되는 것이 아닌 쿼리 저장소에 쌓인다. 그런 뒤 코드 상의 DB에 대한 요청이 모두 끝났을 때 전송된다. 쓰기가 지연되었다고 쓰기 지연이다.

변경 감지

EntityManager는 DB에서 Entity를 가져올 때, 스냅샷을 찍는다. 이후 영속성 컨텍스트에 올라와 있는 Entity가 변경되어 스냅샷과 드리게 되면 쿼리 저장소에 변경과 관련된 쿼리문이 쌓인다.
즉,

User user = find(User.class, 1);
user.setName("김동욱");

만 하면 id가 1번인 User의 이름이 김동욱으로 바뀐다.

Flush

그럼 어플리케이션이 요청을 다 처리할 때까지 DB에 대한 수정은 불가능한 것인가?
당연히 아니다.

쿼리 저장소에 쌓인 쿼리문들이 DB로 흘러갈 때, flush 함수가 호출된다. 즉, 요청이 다 처리되기도 전에 DB에 대한 수정을 하고 싶으면 직접 코드 상에서 flush 함수를 호출하면 된다.

Entity 생명주기

비영속
엔티티 객체를 생성했지만 아직 영속성 컨텍스트에 저장하지 않은 상태를 비영속(new/transient)라 한다.

Member member = new Member();

영속
엔티티 매니저를 통해서 엔티티를 영속성 컨텍스트에 저장한 상태를 말하며 영속성 컨텍스트에 의해 관리된다는 뜻이다.

em.persist(member);

준영속
영속성 컨텍스트가 관리하던 영속 상태의 엔티티 더이상 관리하지 않으면 준영속 상태가 된다. 특정 엔티티를 준영속 상태로 만드려면 em.datach()를 호출하면 된다.

// 엔티티를 영속성 컨텍스트에서 분리해 준영속 상태로 만든다.
em.detach(member);
// 영속성 콘텍스트를 비워도 관리되던 엔티티는 준영속 상태가 된다.
em.clear();
// 영속성 콘텍스트를 종료해도 관리되던 엔티티는 준영속 상태가 된다.
em.close();

준영속 상태의 특징
1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않는다.
식별자 값을 가지고 있다.

삭제
엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제한다.

em.remove(member);

profile
nestjs 백엔드 개발합니다.

0개의 댓글