본문 바로가기

Spring/JPA

자바 ORM 표준 JPA 프로그래밍 #2(영속성)

들어가며

JPA에서 제공하는 기능은 크게 엔티티와 테이블을 매핑하는 설계 부분과 매핑한 엔티티를 실제 사용하는 부분으로 나눌 수 있다. 엔티티 매니저는 엔티티를 저장하고, 수정, 삭제, 조회하는 등 엔티티와 관련된 모든 일을 처리한다. 이름 그대로 엔티티의 관리자의 역할을 한다. 개발자의 역할에서 보면 엔티티를 저장하는 가상의 데이터베이스로 보면 될 것 같다.

 

엔티티 매니저 팩토리와 엔티티 매니저

엔티티 매니저 팩토리는 이름 그대로 엔티티 매니저를 만드는 공장인데, 이 공장을 만드는 비용은 상당히 크다. 따라서 한 개만 만들어 애플리케이션 전체에서 공유하도록 설계되어 있다. 일반적으로 애플리케이션은 엔티티 매니저 팩토리를 한개만 생성한다. 

 

엔티티 매니저 팩토리의 특징

위의 설명을 바탕으로 설계된 에티티 매니저 팩토리의 특징을 정리하자면 다음과 같다.

  • 엔티티 매니저 팩토리는 애플리케이션 전체에서 한 번만 생성하고 공유해서 사용해야 함
  • 엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전하다.(엔티티 매니저는 멀티 스레드 환경에서 안전하지 않다)
  • 엔티티 매니저 팩토리를 생성하면 동시에 커넥션 풀도 생성한다. (persistence.xml을 보면 db 접속 정보가 있다)

영속성 컨텍스트

영속성 컨텍스트란 엔티티를 영구적으로 저장하는 환경을 말한다. 엔티티 매니저를 통해 엔티티를 영속성 컨텍스트에 접근시켜 관리한다.

 

엔티티의 생명주기

  • 영속
    영속성 컨텍스트에 저장된 상태 (em.persist)
  • 비영속
    영속성 컨텍스트와 전혀 관계가 없는 상태
  • 준영속
    영속성 컨텍스트에 저장되었다가 분리된 상태(em.detach, em.clear, em.close)
  • 삭제
    영속성 컨텍스트에서 삭제된 상태(em.remove)

영속성 컨텍스트의 특징

영속성 컨텍스트는 엔티티를 식별자값(@Id)로 구분하여 Map으로 저장을 한다. 영속성 컨텍스트의 내부에는 1차 캐시가 존재하는데 엔티티 저장시 Map으로 1차 캐시에 저장하며 조회시 db에 접근하기 전에 1차 캐시에서 조회를 한다.

 

영속 엔티티의 동일성을 보장한다. 

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");

위의 코드에서 a 와 b는 동일한 객체로 보장 받는다. 

 

엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 쿼리를 쌓아두었다가 flush가 발생하면 쌓아둔 쿼리를 데이터베이스에 날린다.

begin();

save(A);
save(B);
save(C);

commit();

위의 로직이 작동하는 순서를 보자

1. 영속성 컨텍스트의 1차캐시에 A,B,C모두 영속화를 한다.  

2. 쓰기지연 저장소에 INSERT 쿼리를 저장한다.

3. 커밋이 발생하면 플러시가 호출 되어 쓰기 지연 SQL 저장소의 쿼리문이 모드 데이터베이스에 날아간다.

 

JPA는 플러시가 발동할 때 영속 상태에 있는 엔티티의 변경에 대해 감지를 하여 자동으로 이에 해당하는 수정쿼리를 만들어 준다.

엔티티를 영속성 컨텍스트에 보관할 때 최초의 상태를 복사해두어 1차캐시 내에 저장을 하는데, 이를 스냅샷이라고 한다.

따라서 엔티티의 수정이 일어날 경우 변경사항과 스냅샷을 비교하여 쿼리문을 만들어 쓰기 지연 SQL 저장소에 해당 쿼리를 저장하고 데이터베이스에 쿼리를 날리게 된다.

 

정리하자면 영속성 컨텍스트가 엔티티를 관리할 경우 이점은 다음과 같다.

  • 1차 캐시
  • 동일성 보장
  • 트랜잭션을 지원하는 쓰기 지연
  • 변경 감지
  • 지연 로딩

플러시

플러시는 영속성 컨텍스트의 변경 내용을 데이터 베이스에 반영한다. 플러시가 발동하게 되면 다음과 같은 일이 일어나게 된다.

1. 변경 감지가 작동해 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해 수정된 엔티티를 찾아 수정쿼리를 만들어 쓰기 지연 SQL 저장소에 등록한다.

2. 쓰기지연 SQL 저장소에 있는 쿼리를 모두 데이터베이스에 전송한다(등록, 수정, 삭제 쿼리)

 

플러시가 발동하는 조건은 다음과 같다.

1. flush를 직접 호출할 때

2. 트랜잭션 커밋을 할 때

3. JPQL을 실행할 때(JPQL을 실행하기 직전 영속성 컨텍스트에는 있지만 데이터베이스에는 반영되지 않은 값에 접근하는 상황이 생길 수 있기 때문에 JPQL을 날리기 직전 플러시를 발동함)