JPA 03 - Persistence


Java, P.. 뭐요?

JPA는 자바 애플리케이션에서 RDBMS와의 상호작용을 쉽게 처리할 수 있도록 도와주는 자바 표준 API이다. 약자로 풀면 Java Persistence API, 자바 영속성 API라 한다. 아니, 영속성이란게 무엇이길래 JPA가 RDBMS랑 상호작용하여 처리할 수 있는 것일까? 이에 대해 영속성이란 것을 알아본다.

Persistence, 영속성

영속성(Persistence)의 사전적 의미는 “오래도록 또는 영원히 계속되는 성질이나 능력” 이라는 의미이다. 그러니까 프로그램에서의 의미를 둔다면, 계속 남아있는 것 즉 데이터를 영구적으로 저장소에 남기는 것을 의미한다.
그래서 Java Persistence API는 자바 애플리케이션에서 데이터베이스와 상호작용하며 객체를 영속화할 수 있는 표준 인터페이스를 제공하는 API이다. 그렇기에 RDBMS랑 상호작용하여 알아서 DB관련 작업을 처리할 수 있다.

영속성 콘텍스트

이러한 영속성의 대상이 되는 것을 엔티티(Entity)라 하며 객체와 테이블의 맵핑이 되는 대상이다. 이 대상을 관리하는 환경을 영속성 콘텍스트라 한다. 애플리케이션과 DB 사이에서 객체를 보관하는 가상의 데이터베이스 같은 역할을 하며, 엔티티 매니저를 통해 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리하게 된다.

엔티티의 생명주기

엔티티는 영속성 콘텍스트안에서 엔티티 매니저를 통해 관리가 된다. 이 때 엔티티의 생명주기를 살펴보면 다음 그림과 같다.

lifeCycle

  • 비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 상태
  • 영속(managed): 영속성 컨텍스트에 저장된 상태
  • 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제(removed): 삭제된 상태

비영속(new)

엔티티 객체는 생성 되었지만 아직 영속성 컨텍스트에 반영이 안된 상태

Member member = new Member();

영속(managed)

엔티티 매니저를 통해 엔티티가 영속성 컨텍스트에 등록된 상태. 이제 이 겍체는 영속성 컨텍스트에 의해 관리가 되어 진다.

em.persist(member);

준영속(detached)

영속성 컨텍스트에 등록된 엔티티가 영속성 컨텍스트에 떨어져 나간 상태.

// member Entity를 영속성 컨텍스트에서 분리하여 준영속
em.detach(member);
// 영속성 콘텍스트를 비우면 영속된 모든 엔티티들은 준영속 
em.clear();
// 영속성 콘텍스트를 종료해도 영속된 모든 엔티티들은 준영속
em.close();

이렇게 되면 해당 엔티티는 영속성 컨텍스트의 특징들을 이용하지는 못한다. 다만, 다시 영속성 컨텍스트에 등록할 수있다.

// member 엔티티를 다시 영속화
em.merge(member);

삭제(removed)

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

em.remove(member);

영속성 컨텍스트 특징

DB를 직접 읽고 쓰는건 성능에 있어서 큰 비용이다. 이 성능 비용을 줄이기 위해 아래와 같은 영속성 컨텍스트의 특징을 이용하여 JPA를 사용한다.

  • 1차 캐시
  • 쓰기 지연
  • 변경 감지
  • 플러쉬

1차 캐시

영속성 컨텍스트의 내부적인 캐시 맵. 식별자와 엔티티 객체로 이루어진 구조로, 영속상태의 엔티티를 저장한다. 엔티티 객체를 찾을 때(find()) 사용되며 흐름은 다음과 같다.

  1. 1차 캐시에서 엔티티를 찾기
    • 있으면 메모리에 있는 1차 캐시에서 엔티티를 조회
    • 없으면 데이터베이스에서 조회하여 엔티티를 생성해 1차 캐시에 저장
  2. 조회한 엔티티를 반환한다.

이러한 흐름으로 인하여 성능을 개선하고, 영속 엔티티는 동일성을 보장할 수 있다. (동일성=두 개의 객체의 주소 및 내용이 완전히 같은 경우 / 동등성=두 개의 객체의 주소가 달라도 내용이 같은 경우)

쓰기 지연

바로 DB SQL로 보내는 것 대신, 트랜잭션(Transaction)을 커밋할때까지 영속성 컨텍스트의 내부 쿼리 저장소에 실행돼야하는 SQL문이 전부 저장된다. 그리고 커밋이 되면 모아둔 쿼리를 한번에 DB에 보낸다. 이를 쓰기 지연이라 하며 SQL 보내는 작업을 최소화 시켜준다.

변경 감지

엔티티를 수정할 경우 바로 DB에 수정하는 것이 아닌, 영속성 컨텍스트에 영속되있는 엔티티를 조회하여 변경됨을 감지하는 것이다. 변경 감지 흐름은 다음과 같다.

  1. 트랙잭션을 커밋하면 엔티티 매니저 내부에서 먼저 플러시가 호출
  2. 엔티티와 스냅샷(엔티티 영속시 최초 상태)을 비교하여 변경된 엔티티를 찾기
  3. 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 저장
  4. 쓰기 지연 저장소의 SQL을 플러시
  5. DB 트랜잭션을 커밋

플러쉬

변경 감지에서 말한 플러시는 영속성 컨텍스트의 변경 내용을 DB에 반영하여 동기화해주는 메서드이다. 플러시가 호출되는 흐름은 다음과 같다.

  1. 변경 감지가 동작해서 스냅샷과 비교해서 수정된 엔티티를 찾기.
  2. 수정된 엔티티에 대해서 수정 쿼리를 만들고, SQL 저장소에 등록.
  3. 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송.

플러시를 호출하는 방법 다음과 같다.

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