JPA #2 Persistence Context(영속성 컨텍스트)

함형주·2022년 10월 22일
0

JPA

목록 보기
2/7

질문, 피드백 등 모든 댓글 환영합니다.

JPA의 핵심이라고 할 수 있는 영속성 컨텍스트에 대해 정리하겠습니다.

Persistence Context(영속성 컨텍스트)

영속성 컨텍스트가 지닌 의미를 알아보겠습니다.

Persistence(영속성)은 프로그램의 실행이 종료되더라도 관련된 데이터가 사라지지 않는 특징을 말합니다. Context는 맥락, 환경을 의미하므로 정리하자면

Persistence Context : 데이터를 영구적으로 저장하는 환경으로 생각할 수 있습니다.

JPA는 영속성 컨텍스트를 이용하여 Entity를 DB와 연결하여 관리합니다.

Entity : 테이블과 1:1로 매핑되는 객체

JPA를 사용하려면 Gradle이나 Maven에 의존성을 추가하고 DB도 지정해야 합니다.
설정하는 과정은 블로그 참조해주세요.

영속성 컨텍스트에 접근하기 위해선 EntityManager를 사용해야 하고 EntityManagerFactory를 통해 생성할 수 있습니다.

EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
EntityManager em = emf.createEntityManager();

이렇게 생성한 EntityManager를 통해 영속성 컨텍스트를 사용할 수 있습니다.

DB에 요청이 발생할 때 마다 생성되며 스프링 프레임워크 같은 컨테이너 환경에선 EntityManager와 영속성 컨텍스트는 N:1 로 연결됩니다.

Entity의 생명주기

영속성 컨텍스트가 관리하는 엔티티의 생명주기는 4가지가 있습니다.

@Entity
public class User { // 예제에서 사용할 엔티티
	@Id Long id;
	String name;
    int age;
}
  1. 비영속
  • User user = new User() - 객체가 생성되고 영속성 컨텍스트와는 관련이 없는 상태
  1. 영속
  • em.persist(user) - 객체가 영속성 컨텍스트에 저장 됨. 혹은 em.find()로 영속성 컨텍스트를 통해 DB에서 값을 가져온 경우에도 그 엔티티를 영속화 시킴
    (참고로 엔티티의 CUD는 트랜잭션 안에서 이루어져야 하므로 em.persist()전에 em.getTransaction().begin() 으로 트랜잭션을 실행해야 함. )
  1. 준영속
  • em.detached(user), em.clear(), em.close()로 인해 영속성 컨텍스트에서 분리된 상태
  1. 삭제
  • em.remove(user) - 삭제

영속성 컨텍스트의 장점

그렇다면 영속성 컨텍스트를 이용하여 엔티티를 관리하면 좋은 점이 무엇일까요?

1차 캐시

  • 영속성 컨텍스트에 내부에 MultiValueMap 구조의 1차 캐시를 구현합니다. key값은 엔티티의 Id, value는 객체와 스냅샷을 저장합니다.(스냅샷은 2번에서 설명)

  • em.persist()로 영속화하거나 em.find()를 통해 영속화 한 경우 해당 엔티티를 1차 캐시에 저장합니다.

  • 1차 캐시로 인한 장점은 영속화된 엔티티를 조회할 때 DB에 접근하지 않고 1차 캐시에서 접근하여 SQL 쿼리를 발생시키지 않고 조회할 수 있습니다.
    em.find(User.class, 1L) (1L은 id값) 으로 엔티티를 DB에서 가져올 수 있는데 find() 메서드가 실행되면 JPA는 영속성 컨텍스트의 1차 캐시에서 key값으로 조회 후 없다면 DB에 select 쿼리를 발생시킵니다.

  • 영속화 상태의 엔티티 각각 조회 시 동일성을 보장합니다.
    em.find(User.class, 1L) == em.find(User.class, 1L) 결과 true

변경 감지(Dirty Checking)

  • 영속화된 엔티티에 수정 사항이 있으면 별도의 명령없이 수정 사항이 DB에 반영하는 것을 변경 감지라고 합니다.
    user.setName("userB")를 실행하고 transaction.commit() (SQL 쿼리 생성)하는 시점에 1차 캐시에서 엔티티와 스냅샷을 비교합니다. 스냅샷은 엔티티가 영속화된 최초의 상태를 의미합니다. 엔티티와 스냅샷을 비교 후 수정 사항이 있으면 Update 쿼리를 생성합니다.

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

  • em.persist()를 앞서 소개할 때 엔티티를 DB에 저장하는 것이 아니라 영속성 컨텍스트에 저장한다는 표현을 사용했습니다. 실제로 별도로 설정하지 않는다면 persist()를 호출하는 시점에 쿼리를 발생시키는 것이 아니라 특정 시점까지 기다렸다가 SQL 쿼리를 실행시킵니다.

  • 영속성 컨텍스트(EntityManager)를 flush 하는 시점에 SQL 쿼리가 발생합니다.
    flush는 영속성 컨텍스트의 변경사항(생성, 수정, 삭제)을 DB에 반영하는 것을 의미합니다.
    em.flush()로 직접 flush 하거나 transaction.commit(), 그리고 JPQL을 실행하면 영속성 컨텍스트를 flush 할 수 있습니다. (JPQL은 JPA가 제공하는 객체 기반의 SQL문법입니다. em.persist(user)로 객체를 영속화 시킨 있는 상태에서 select *을 실행하면 flush 하여 영속화 된 객체를 DB에 반영시키고 그 이후에 DB를 조회합니다.)

프록시

  • DB에서 엔티티를 조회할 경우 객체가 가진 모든 정보에 대해 select 쿼리를 발생시켜야 할까요?

  • 예를 들어 게시판 예제의 경우 User 객체를 조회할 때 연관된 객체를 함께 조회해야 하는 지 생각해야 합니다.
    단순히 User의 이름만 필요한 상황에서 User가 작성한 글, 댓글 등을 함께 조회하게 되면 SQL 쿼리 분량이 많아져 성능 이슈가 생길 수 있고 애플리케이션 내부에서도 미리 작성한 글, 댓글들에 대한 객체를 만들어야 하기에 매우 비효율적일 것입니다.

  • 상황 상 User와 연관된 모든 객체가 필요할 수도 있지만 대부분의 경우는 아닐 것입니다.
    이러한 문제를 해결하기 위해서 영속성 컨텍스트는 프록시 기능을 제공합니다.

  • 프록시는 객체의 조회를 미루기 위한 기능이라고 할 수 있습니다. 프록시를 이용하여 엔티티와 연관된 객체의 조회를 미루는 것을 지연로딩이라고 합니다.
    지연로딩으로 엔티티를 em.find()로 조회 시, 엔티티와 연관된 객체는 해당 객체를 상속 받은 프록시 객체를 생성하여 제공합니다.
    또한 프록시의 기능을 활용하여 영속성 전이, orphanRemovel 등을 설정하여 엔티티의 생명주기 관리가 가능하고 도메인 주도 설계 시 Aggregate Root를 구현하는데 유용합니다.

  • 프록시 객체는 원본 객체의 참조 값을 보관하는데 프록시 객체를 호출하면 그 시점에 DB에서 실제 엔티티를 조회합니다.

  • 프록시 객체 생성 및 요청 흐름
    User 조회 (User는 select 쿼리로 즉시 조회후 User와 연관된 Xxx 프록시 객체 생성)
    -> user.getxxx()로 프록시 객체에 접근 (프록시 객체는 원본 객체의 자식이므로 원본 객체 타입으로 사용 가능)
    -> DB에서 실제 엔티티 호출

profile
평범한 대학생의 공부 일기?

0개의 댓글