JPA 영속성(Persistence) 정리

2020년 10월 28일 (1년 전)
JPA를 그동안 사용하면서 잘 알지 못하고 사용해왔는데, JPA에 대해서 제대로 알기 위해서 가장 중요한 개념인 영속성(Persistence)에 대해서 정리해보려고 한다.

영속성 컨텍스트(Persistence Context) 의미

엔티티가 영구 지속되는 환경.

눈에 보이거나 하는 것은 아니기 때문에 논리적인 개념이라고 표현한다.

국어사전을 뒤져 보았다.

그냥 내가 단어의 뜻이 궁금해서 한번 찾아 보았는데, 말이 되도록 끼워 맞춰보면 (무언가가) 오래도록 지속되는 환경 정도로 해석할 수 있을 것 같다.

image image

영속성 컨텍스트의 구조

1차 캐시 저장소, SQL 저장소로 나뉘어 진다.

image

1차 캐시 저장소

엔티티를 조회하거나 저장할 경우 해당 엔티티는 1차 캐시에 저장된다.

1차 캐시는 Map형태(Key-Value)로 보관이 되는데 식별자값(Key)을 통해서 엔티티를 조회해서 1차 캐시에 없을 경우 DB 조회 후 생성해서 넣고, 있을 경우 1차 캐시에 엔티티를 반환해준다. 그래서 Map을 통해 같은 엔티티를 반환하기에 a==b같은 동일성을 보장해준다.

1차 캐시의 동작 순서

  1. 최초 조회할 때는 1차 캐시에 엔티티가 없다.
  2. DB에서 엔티티를 조회한다.
  3. 엔티티를 1차 캐시에 보관한다. 이 때 최초 값을 따로 저장해두는 데, 이 값을 스냅샷이라고 한다.
  4. 1차 캐시에 보관된 결과를 반환한다.
  5. 이후 같은 식별자를 통해 엔티티를 조회하면 DB 조회없이 1차 캐시에 엔티티를 그대로 반환한다.

SQL 저장소

여러 SQL을 저장했다가 트랜잭션을 commit할 때 한번에 처리한다. 이를 통해 DB 접근 횟수를 최소화하고 성능상 이점을 얻을 수 있다.

엔티티 매니저(Entity Manager)와 엔티티 매니저 공장(Entity Manager Factory)

영속성 컨텍스트를 위해 일하는 녀석과 그런 녀석을 만드는 녀석.

Entity Manager

엔티티(Enitty)를 영속성 컨텍스트에 저장(persist)하고, 조회(find)할 때 영속성 컨텍스트에 보관하고 관리하는 역할을 하는 녀석이다. 그리고 엔티티 매니저는 특정 시점까지 DB 커넥션을 얻지 않고, 스레드 간 공유 시 동시성 문제가 발생할 수 있다.

Entity Manager Factory

엔티티 매니저 공장은 생성 비용이 크기 때문에 보통 하나의 DB를 사용할 경우 하나만 생성해서 사용한다. 대신 엔티티 매니저는 공장을 통해서 요청마다 엔티티 매니저가 만들어져 나오기 때문에 여러 개가 존재할 수 있다. 그리고 엔티티 매니저 공장은 스레드 간 공유해도 안전하다. image

엔티티(Entity)의 생명 주기(Life Cycle)

영속성에 대해서 이해하기 위해서는 우선 엔티티의 생명주기를 알아야 한다. image

비영속(new/transient)

엔티티를 생성만 하고, 아무것도 하지 않은 상태. 아직은 영속성 컨텍스트와 전혀 관계가 없다.

Member member = new Member();

영속(managed)

영속성 컨텍스트에 엔티티가 저장된 상태.

이때부터 엔티티가 영속성 컨텍스트에 의해 관리된다. 영속화 한다고 바로 DB에 저장이 되는 것은 아니고, 트랜잭션 commit 시점에 영속성 컨텍스트에 있는 정보들이 이 때 SQL이 실행되어 DB에 저장이 된다.

em.persist(member);

준영속(detached)

영속성 컨텍스트에 엔티티가 저장되었다가 분리된 상태.

준영속 상태를 만드는 방법

영속 상태인 엔티티를 준영속 상태로 바꾸는 방법은 영속성 컨텍스트에서 분리하거나, 엔티티 매니저를 초기화 및 종료하여 엔티티가 관리되지 않게 하는 방법이 있다.

//1. 해당 엔티티를 준영속 상태로 변경
em.detach(member)
//2. 영속성 컨텍스트 초기화
em.clear()
//3. 영속성 컨텍스트 종료
em.close()

그럼 준영속 상태에서 영속 상태로 바꾸는 방법은?

병합(merge)을 사용하면 된다 merge메소드는 준영속 상태의 엔티티를 받아서 영속 상태의 엔티티를 반환하게 된다. 병합은 해당 엔티티로 모든 값을 교체하기 때문에 엔티티의 값이 없으면 null을 업데이트할 위험이 있다. 준영속 전환이 아닌 엔티티 변경이 목적일 경우 병합 대신 변경 감지를 이용하는 것이 좋다.

전환 순서

  1. 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회
  2. 만약 1차 캐시에 엔티티가 없으면 데이터베이스에서 엔티티를 조회하고 1차 캐시에 저장
  3. 조회한 영속 엔티티에 파라미터로 넘어온 비영속 엔티티의 값을 채워 넣는다.
  4. 새로운 영속 엔티티를 반환
em.merge(member)

비영속과의 차이점?

엔티티 매니저를 통한 영속성 컨텍스트의 지원을 하나도 받지 못한다는 건 동일한데, 영속 상태가 되었었기 때문에 준영속 상태는 식별자 값이 존재한다는 차이가 있다.

삭제(removed)

영속성 컨텍스트에서 엔티티가 삭제된 상태.

엔티티가 삭제되면 DB에서도 삭제된다.

em.remove(member);

플러시(flush)

영속성 컨텍스트의 변경 내용을 DB에 반영하는 것

플러시의 동작 순서

  1. 변경 감지 동작 실행.
  2. SQL저장소의 SQL들을 DB에 전송(SELECT, UPDATE… 등 쿼리 실행).

변경 감지

JPA에서 Update를 하려고 하면 수정을 지원하는 메소드가 없다. 그 이유는 바로 변경 감지를 지원하기 때문인데, 엔티티를 스냅샷과 비교해서 바뀐 것이 있으면 수정 쿼리를 생성해서 SQL저장소에 등록한다. flush는 플러시할 때에만 동작한다.

JPA UPDATE 전략

필드가 몇개가 바뀌든 그냥 전체 필드를 업데이트 친다. 그렇기 때문에 쿼리가 항상 동일하고, 이전에 업데이트 친 경우가 있을 경우 이전 쿼리를 재사용 한다.

플러시 방법

총 3가지 방법이 있는데, 보통 트랜잭션을 커밋해서 플러시가 자동으로 호출되도록 하고, flush메소드를 직접 호출하는 경우는 테스트할 때를 제외하고는 거의 없다.

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

참고

자바 ORM 표준 JPA 프로그래밍 책

태그#Jpa#Java