[이론] 영속성 컨텍스트

ds_chanin

·

2019. 3. 29. 23:54


영속성 컨텍스트?

JPA를 다루기전에 영속성 컨텍스트에 대한 이해가 필요하다!

그리고 영속성 컨텍스트를 이해하기 위해 엔티티 매니저에 대한 이해가 필요하다!

 

엔티티 매니저

엔티티 매니저는 엔티티 매니저 팩토리에서 생성된다.

엔티티 매니저 팩토리는 애플리케이션 전체에서 딱 한 번만 생성되고 공유되는데

그 이유는 팩토리의 생성비용이 크기 때문이다.

이러한 팩토리는 여러 명이 동시에 접근을 해도 무방하다!

엔티티 매니저는 당연히 위에서 언급한 팩토리에서 생성된다.

JPA의 기능 대부분을 엔티티 매니저가 제공해준다.

즉, 엔티티 매니저가 데이터베이스의 CRUD작업을 제공한다.

이러한 엔티티 매니저는 팩토리와 달리 여러 명이 하나의 엔티티 매니저에 동시접근을 해선 안된다.

여러 명이 접근하면 동시성 문제가 발생한다!

엔티티 팩토리의 생성비용이 크지만 엔티티 매니저는 생성비용이 거의 안 드는 수준이다.

 

영속성 컨텍스트

영속성 컨텍스트가 무엇인지 알아보자.

내가 공부하는데 사용하는 책에서는 다음과 같이 설명한다.

JPA를 이해하는 데 가장 중요한 용어는 영속성 컨텍스트다. 우리말로 번역하기가 어렵지만 해석하자면 '엔티티를 영구 저장하는 환경'이라는 뜻이다. 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.

이러한 영속성 컨텍스트의 개념은 논리적인 개념에 가깝기 때문에 당연히 눈에 보이지는 않는다.

나는 엔티티 매니저를 한번더 감싸고 있는 존재라고 이해를 하였다.

 

 

엔티티 생명주기

영속성 컨텍스트의 특징을 설명하기전에 다시 샛길로 빠져서 엔티티의 생명주기에 대해 얘기해보자.

엔티티에는 총 4가지의 상태가 존재한다.

  • 비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 상태

  • 영속(managed): 영속성 컨텍스트에 저장된 상태

  • 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태

  • 삭제(removed): 삭제된 상태

 

 

엔티티 생명 주기를 그림으로 나타내면 다음과 같이된다.

 

 

특징

영속성 컨텍스트의 특징에 대해 알아보자!

 

    1. 영속성 컨텍스트와 식별자 값

      영속성 컨텍스트는 엔티티를 테이블의 ID와 같은 식별자 값으로 구분한다.

      즉 영속 상태에 들어간 엔티티는 식별자 값이 반드시 존재해야 한다.

    2. 영속성 컨텍스트와 데이터베이스 저장

      JPA는 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 저장되어있던 엔티티를 데이터베이스에 반영한다.

      이를 플러시(flush)라고 한다.

영속성 컨텍스트를 이용한 엔티티 관리의 장점

    • 1차 캐시

      영속성 컨텍스트는 내부에 캐시가 있다. 이를 1차 캐시라한다.

      영속 상태에 들어간 엔티티는 1차 캐시에 저장된다.

      엔티티가 캐시에 없을때 데이터베이스에 접근하여 엔티티를 불러오고 캐시에 존재하면 캐시에 있는 엔티티를 불러오기때문에 데이터베이스 접근횟수가 감소한다.

       

    • 동일성 보장

      만약 코드상에서 식별자가 1인 엔티티를 두번 찾는다고 하자.

      User user1 = repo.findByIdx(1);
      User user2 = repo.findByIdx(1);

      위와 같이 생성된 user1, user2 인스턴스는 동일할까?

      System.out.print(user1 == user2);
      //결과는 true

      1차 캐시에 식별자가 1인 엔티티를 불러왔기 때문에 같은 인스턴스이다!

       

    • 트랜잭션을 지원하는 쓰기 지연

      데이터베이스에 접근하는 횟수는 성능과 직결된다고 할 수 있다.

      데이터베이스에 유저등록 작업을 3번한다고 가정하고 두 가지 상황으로 나누어 생각해보자.

      첫번째, 등록요청이 들어올 때 마다 바로 데이터베이스에 등록쿼리를 보내고 마지막에 트랜잭션을 커밋하는 경우.

      두번째, 등록요청이 들어와도 바로 등록쿼리를 데이터베이스로 보내지 말고 메모리에 모아두고 트랜잭션 커밋할 때 모아둔 쿼리를 한 번에 데이터베이스에 보내는 경우.

      두 가지 경우 모두 하나의 트랜잭션 내에서 이루어 졌기때문에 그 결과는 같다.

      하지만 첫번째 경우는 쿼리를 데이터베이스에 보내도 트랜잭션 커밋이 이루어지기 전에는 의미가 없다.

      그렇기에 쓰기 지연을 활용하면 성능을 더 좋게 할 수 있다.

       

    • 변경 감지

      변경 감지는 엔티티의 수정으로 설명할 수 있다.

      엔티티의 정보를 변경하기위에 쿼리문을 작성하는 것은 매우 불편하다.

      쿼리문을 작성하는 방식은 점점 수정쿼리가 늘어나는 경우 좋지 않고비즈니스 로직 분석을 하는 경우

      그 의존도가 SQL에 더 높아진다.

      그래서 변경감지가 어떤 장점인지 코드와 함께 설명을 해보면

      User user = repo.findByIdx(1);
      ​
      user.setUserName("수정될 이름");
      ​
      //이런 식으로 업데이트를 명시적으로 해주지 않아도 된다!
      //repo.update(user);
      //그저 엔티티의 데이터 값만 변경해 주면 끝!

      JPA는 엔티티를 영속성 컨텍스트에 보관할 때 최초 상태를 스냅샷이라는 이름으로 저장한다.

      그리고 플러시 할 때 이 스냅샷과 현재 엔티티를 비교하여 변경상태를 감지한다.

      그렇게 변경상태를 감지하면 수정쿼리를 생성하여 쓰기지연 저장소에 보내고 커밋 시점에서 데이터베이스에 있는 데이터 또한 변경이 된다.

      이러한 변경감지는 영속 상태의 엔티티에만 적용이 된다.

       

    • 지연 로딩

      지연 로딩을 예를 들어 설명하면 정말 쉽게 이해할 수 있다.

      작년에 프로젝트를 할 때 다음과 비슷한 코드가 있었다.

public getBaggages(Long userIdx){ 
    List<Baggage> baggages = findByUserIdx(userIdx);
    ...
}


public getBaggagesDetails(Long userIdx){
    List<Baggage> baggages = findByUserIdx(userIdx);
    List<BagImg> bagImgs = baggages.getBagImgs();
    ...
}
  • getBaggages 메소드는 Baggage(이하 짐) 리스트는 불러오지만 그와 연관된 이미지를 불러오는 코드는 존재하지 않았다.
    그리고 그아래에 getBaggagesDetails 메소드는 상세정보를 보여주기위해 연관된 BagImg 리스트를 추가로 불러왔다.
    짐 엔티티를 찾아올때 두번째 메소드와 달리 첫번째 메소드의 경우에는 짐 이미지 엔티티는 필요하지 않아 같이 불러오면 이는 불필요한 데이터베이스 접근이 된다.
    JPA는 이러한 문제를 해결하기위해 엔티티가 실제로 사용될 때까지 데이터베이스 조회를 지연하는 방법을 제공한다.