영속성 컨텍스트 (Persistence Context)

땡글이·2023년 2월 14일
0

JPA

목록 보기
1/9

영속성 컨텍스트는 하이버네이트와 같은 객체-관계 매핑 프레임워크가 실시간 어플리케이션에서 엔티티의 생명주기를 관리하기 위해 만들어진 개념이다.

하이버네이트란, 자바 언어를 위한 ORM 프레임워크이다. 즉, 객체 지향 도메인 모델을 관계형 데이터베이스로 매핑시켜준다. 하이버네이트는 JPA의 구현체이다.

또한, 영속성 컨텍스트는 JPA를 이해하는 데에 있어서 가장 중요한 개념이다. 이는 Entity를 영구 저장하는 환경이라는 의미를 가지고, 내부에서는 Entity의 생명주기를 관리한다.
실제 스프링을 이용한 서버 어플리케이션은 다중 쓰레드 환경이기에 요청이 있을 때마다 EntityManger 인스턴스를 생성해서 영속성 컨텍스트N:1 관계를 가진다.

EntityManagerFactory 와 EntityManger


EntityManagerFactory는 사용자의 요청이 있을 때마다 EntityManager 인스턴스를 생성해준다. EntityManager 는 영속성 컨텍스트를 이용해 DB 에 접근한다.

  • EntityManagerFactory
    • WAS 가 종료되는 시점에 EntityManagerFactory 를 종료된다. 그래야 내부적으로 Connection pooling 에 대한 Resource 가 Release 된다.
  • EntityManager
    • 실제 Transaction 단위를 수행할 때마다 생성한다.
    • Transaction 수행 후에는 반드시 EntityManager 를 닫는다. 그래야 DB Connection 이 반환되고 다른 EntityManager 들도 DB에 접근할 수 있다.
    • EntityManager.persist(entity)l 를 통해, 엔티티를 영속성 컨텍스트에 저장한다.

영속성 컨텍스트에 저장되었다고 해서, DB에 반영된 상태는 아니다. 트랜잭션이 커밋되어야 DB에 수정내용이 반영된다. 이유는 영속성 컨텍스트의 1차 캐시쓰기 지연에 있다. 뒤에서 "영속성 컨텍스트의 이점" 에 대해 다루며 설명하겠다.

Entity의 생명주기

Entity는 총 4가지의 상태(영속, 비영속, 준영속, 삭제)를 가진다.

영속 (managed)

  • 영속성 컨텍스트에서 저장되어 있고, 관리되는 상태
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();

// 객체를 생성한 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

// 객체를 저장한 상태 (영속)
entityManager.persist(member);

transaction.commit();

비영속 (new/transient)

  • 영속성 컨텍스트와 전혀 관계가 없는 상태
  • 객체를 생성만 한 상태이다.
// 객체를 생성한 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

준영속 (detached)

  • 영속성 컨텍스트에서 관리되다가 분리된 상태
  • 즉, 영속성 컨텍스트의 관리를 받지 않는다. 그렇기에 필드를 수정해도 영속성 컨텍스트의 변경 감지 기능이 작동하지 않고 DB에도 반영되지 않는다.
// 회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
entityManager.detach(member);

삭제 (remove)

  • 실제 DB에 삭제를 요청한 상태
// 객체를 삭제한 상태
entityManager.remove(member);

영속성 컨텍스트의 이점

위에서 영속성 컨텍스트를 이해하기 위한 배경지식들을 살펴보았다. 이제 영속성 컨텍스트에서는 왜 위처럼 4가지 상태로 분리해서 엔티티를 관리하는지 어떤 이점을 가지는지 알아보겠다.

1차 캐시

만약 엔티티를 영속화시켜 영속성 컨텍스트 내에서 관리하도록 만들어주면, 영속성 컨텍스트에서는 1차 캐시에 해당 내용을 담아둔다.

// 객체를 생성한 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

// 객체를 저장한 상태 (영속)
em.persist(member);

// 1차 캐시에서 조회됨.
Member findMember = em.find(Member.class, "member1"); 

만약, 같은 트랜잭션 내에서 커밋되기 전 엔티티를 조회한다면 DB에서 select 쿼리를 날려 가져오는 것이 아니라 1차 캐시에서 조회된다.

Member findMember1 = em.find(Member.class, 101L);	// DB에서 조회
Member findMember2 = em.find(Member.class, 101L);	// 1차 캐시에서 조회
System.out.println(findMember1.getId());

하지만, 기본키 생성 전략이 IDENTITY로 DB에 저장이 되어야지만 PK를 알 수 있는 상태라면, 커밋되기 전에도 insert 쿼리문을 날린다.

2차 캐시

고객의 요청에 맞는 응답을 해준 뒤에는 영속성 컨텍스트 내부를 비우기 때문에 1차 캐시가 성능 상 큰 이점을 가지지는 못한다. 애플리케이션에서 전체적으로 공유하는 캐시(요청을 리턴해준 뒤에도 유지됨)는 JPA나 Hibernate 에서 2차 캐시라고 부른다.

2차 캐시에 대한 내용은 해당 포스팅을 참고하자. (따로 포스팅할 예정)

영속 엔티티의 동일성 보장

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

Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L);
            
System.out.println(findMember1 == findMember2);   // true

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

em.persist() 를 통해 엔티티를 영속화시켜도 INSERT SQL 문을 DB로 보내지 않는다. 커밋하는 순간에 DB로 INSERT SQL문을 보내고 DB에 변경내용이 반영된다.
영속성 컨텍스트 내에는 쓰기 지연 SQL 저장소가 있다. 해당 저장소에 DB 변경 내용이 담긴 SQL문들을 저장해두고 한 번에 DB로 보냄으로써 성능 상의 이점을 가져간다. 하지만, 성능 상 큰 이점이 되지는 못한다고 한다.

변경 감지 (Dirty Checking)

영속성 컨텍스트 내에서 관리되는 영속 엔티티는 트랜잭션 내에서 필드 값이 변경되면, em.persist() 를 호출하지 않아도, 트랜잭션이 종료될 때 영속성 컨텍스트의 1차 캐시 안에 있는 엔티티의 스냅샷과 비교한 뒤, 다르면 UPDATE SQL 문이 쓰기 지연 SQL 저장소에 저장된다.

변경 감지(Dirty Checking) 의 문제

여기서 추가적으로 알아가야 하는 부분이 있는데, 변경 감지 옆에 Dirty Checking 이라는 단어를 언급했다. Dirty 는 "상태의 변화가 생긴" 이라는 의미를 가지므로 Dirty Checking은 "상태 변화 검사" 라는 의미를 가진다.
앞서 말했듯 영속성 컨텍스트는 트랜잭션이 끝나고 난 뒤 스냅샷과 엔티티를 비교해 업데이트된 필드를 업데이트한다. 하지만, Dirty Checking으로 생성되는 UPDATE 쿼리는 모든 필드를 업데이트한다.

만약 한 테이블 내에 20~30개의 필드를 가진다면, 조금 문제가 될 수 있다. 그래서 Entity 객체에 @DynamicUpdate 어노테이션을 활용해서, 변경 부분만 업데이트되도록 할 수 있다.

지연 로딩

JPA나 Hibernate 에서는 관계형 데이터베이스를 객체로 매핑해주기에, 데이터베이스에서는 FK 필드를 통해 관계를 가지지만, 자바 어플리케이션에서는 객체를 참조함으로써 연관관계를 가진다.
만약 Member 엔티티 내에서 연관관계의 주인으로서 Team 엔티티를 참조하고 있다면, 논리적으로는 Member 엔티티를 조회할 때에 Team 객체에 대한 정보까지 긁어와야 한다. 하지만, 영속성 컨텍스트에서는 프록시 객체를 활용해서 Team 객체에 접근하지 않으면 Team에 대한 정보는 가져오지 않도록 지연로딩 기능을 제공한다.

즉시 로딩과 지연 로딩에 대해서 추후 자세히 포스팅할 것이다.

Reference

https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
https://www.baeldung.com/jpa-hibernate-persistence-context
https://gmlwjd9405.github.io/2019/08/06/persistence-context.html
https://junghyungil.tistory.com/203
https://steady-coding.tistory.com/607
https://jojoldu.tistory.com/415

profile
꾸벅 🙇‍♂️ 매일매일 한발씩 나아가자잇!

0개의 댓글