Home 영속성 컨텍스트
Post
Cancel

영속성 컨텍스트

JPA는 기본적으로 간단하게는 위와 같은 구조로 동작한다.

  1. 고객의 요청이 있을 때 EntityManagerFactory를 통해 EntityManager를 생성한다.
  2. EntityManager는 내부적으로 데이터베이스 커넥션을 사용하여 DB를 사용한다.

그렇다면 영속성 컨텍스트란 뭘까?

영속성 컨텍스트

JPA를 이해하는데 가장 중요한 용어로 “엔티티를 영구 저장하는 환경” 이라는 뜻이다.

1
EntityManager.persist(entity);

위 코드는 DB에 데이터(entity)를 저장하는 역할을 수행하는 것으로 느껴진다.

하지만 자세히 들여다보면 정확히는 “엔티티를 영속성 컨텍스트에 저장하는 역할”을 수행한다.

  • EntityManager가 생기면 안에 영속성 컨텍스트(PersistenceContext)라는 공간이 생긴다.
  • J2SE 환경에서는 기본적으로 1:1이고 J2EE, 스프링 프레임워크 같은 컨테이너 환경에서는 N:1의 관계를 가진다.

엔티티의 생명 주기

엔티티는 크게 4가지 분류의 생명주기로 나타낼 수 있다.

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

비영속 상태

단순히 객체를 생성한 상태이다. JPA와 관련된 코드를 작성하지 않았으므로 영속성 컨텍스트와는 아무런 관련이 없다.

영속 상태

멤버 객체를 생성했고, EntityManager를 불러온 상태에서 persist() 메서드를 수행한 상태.

EntityManager 내부에 있는 영속성 컨텍스트라는 공간에 관리되는 상태를 뜻한다.

persist() 메서드 수행 시 DB에 저장되는 느낌이지만 DB에 실제로 저장되지 않는다. 즉 쿼리가 날아가지 않는다.

그렇다면 언제 저장되나?

JPA는 트랜잭션 내에서 관리되어야 하는데, 트랜잭션을 커밋할 때 실제로 저장된다.

준영속, 삭제 상태

  • 준영속 상태: 엔티티를 영속성 컨텍스트에서 분리
  • 삭제 상태: DB에서 영구적으로 객체를 삭제

영속성 컨텍스트의 장점

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

1차 캐시

영속성 컨텍스트 내부에서 단순하게 ID를 키값으로, Entity 객체를 value 값으로 1차 캐시를 둔다고 볼 수 있다.

이러한 상태에서 조회를 시도한다면?

DB를 직접 조회하는 것이 아닌 1차 캐시를 우선적으로 접근한다. 1차 캐시에 찾는 값이 있다면 단순하게 1차 캐시에서 값을 꺼내오면 된다.

만약 1차 캐시에 없으면 어떤 과정을 거칠까?

1차 캐시를 조회했는데 값이 없다면, DB에서 그 값을 조회하는 과정을 거치고 그 값을 영속성 컨텍스트 내부의 1차 캐시에 저장하고 반환한다.

이런 과정에서 성능 향상 및 객체지향적인 코드를 작성할 수 있다는 장점을 노릴 수 있다.

동일성 보장

단순하게 “member1” 이라는 이름을 가진 Memeber 객체를 영속성 컨텍스트에 영속 시켰고, find 메서드를 통해 “memrber1” 이라는 이름을 가진 객체를 반환했다면

처음 “member1”이라는 이름을 가진 Member 객체와 find() 메서드를 통해 찾은 Member 객체는 동일성을 가진다.

(equlas and hashcode를 작성하지 않은 상태에서는 다른 객체일 것 같지만 이러한 부분에서 동일성을 보장해준다는 것이다.)

  • 1차 캐시로 반복 가능한 읽기(Repeatable read) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공한다.

엔티티 등록 - 트랜잭션을 지원하는 쓰기 지연

위에서 기술했던 내용이다.

  • JPA, EntityManager는 데이터 변경 시 트랜잭션 내에서 이루어져야 한다.
  • 영속성 컨텍스트에 영속시켰다고 데이터베이스에 SQL 쿼리를 보내지 않고 커밋하는 순간 SQL을 보낸다.

  • persist()를 하는 순간 각 엔티티를 1차 캐시에 저장하고 동시에 쓰기 지연 SQL 저장소라는 곳에 각 SQL을 저장해둔다.

  • commit을 하는 순간 쓰기 지연 SQL 저장소에 있던 SQL 쿼리가 데이터베이스에 전달되고 실제로 커밋된다.

엔티티 수정 - 변경 감지

영속되어 있는 Entity를 조회해 꺼내고 setter 함수를 이용해 정보를 변경한다면 어떻게 될까?

  • commit() 메서드가 실행되면 내부적으로 flush() 메서드가 수행된다.
  • 그러면 엔티티와 기존 엔티티 스냅샷을 비교하게 된다.
    • 우리는 엔티티를 꺼내 값을 변경했기 때문에 기존 엔티티 스냅샷과 정보가 다를 것이다.
    • 변경을 감지했기 때문에 Update 쿼리를 쓰기 지연 SQL 저장소에 저장한다.
  • update 쿼리를 데이터베이스에 전달하게 되고 커밋을 하게 된다.

즉, 영속성 컨텍스트의 엔티티를 꺼내 값만 변경하면 실제로 변경이 적용되는 것이다. 마치 자바에서 컬렉션에 있는 객체를 꺼내 값만 변경하면 실제로 그 객체의 값이 변경되는 것과 비슷한 구조인 것이다.

삭제

위와 비슷한 구조로 삭제가 진행된다. 마지막에는 Delete 쿼리가 데이터베이스에 전달되는 것이다.

플러시

영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 작업.

트랜잭션 커밋이 일어날 때 플러시가 발생하며 플러시가 발생하면 아래와 같은 작업이 수행된다.

  • 변경 감지
  • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
  • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송(등록, 수정, 삭제 쿼리)

플러시하는 방법

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

JPQL 쿼리 실행 시 플러시가 자동 호출되는 이유

간단하게 설명하자면,

예를 들어 위와 같은 코드가 실행됐다고 가정하자.

만약 플러시가 자동 호출되지 않으면 persist한 Member 엔티티들이 DB에 반영되지 않아있을 것이고 JPQL의 Member들을 조회하는 코드는 의도한대로 작동하지 않을 것이다.

이러한 부분을 방지하고자 플러시가 자동 호출된다고 보면 된다.

플러시 모드 옵션

1
em.setFlushMode(FlushModeType.COMMIT)
  • FlushModeType.AUTO: 기본 값
    • 커밋이나 쿼리를 실행할 때 플러시
  • FlushModeType.COMMIT
    • 커밋할 때만 플러시

주의 사항

  • 플러시는 영속성 컨텍스트를 비우는 것이 아니다.
  • 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 것이다.
  • 트랜잭션이라는 작업 단위에 주목해야 한다. -> 커밋 직전에만 동기화하면 된다.
    • 플러시도 결국 트랜잭션이라는 단위가 있기 때문에 이러한 메커니즘이 가능한 것이다.

준영속 상태

영속 상태의 엔티티가 영속성 컨텍스트에서 분리된 상태를 의미한다. 영속성 컨텍스트의 관리에서 벗어나는 상태.

영속성 컨텍스트가 제공하는 기능을 사용하지 못한다.

1
2
3
4
Member member = em.find(Member.class, 150L);
member.setName("AAAAA");

em.detach(member);

위와 같은 코드가 있다면, 커밋을 해도 update 쿼리가 날아가지 않는다.

이론적으로는 이렇고, 구체적으로 사용되는 부분은 웹 애플리케이션을 개발할 때 알아보도록 한다.

준영속 상태로 만드는 법

  • em.detach(entity): 특정 entity만 준영속 상태로 전환한다.
  • em.clear(): 영속성 컨텍스트를 완전히 초기화한다.
  • em.close(): 영속성 컨텍스트를 종료한다.
This post is licensed under CC BY 4.0 by the author.