본문 바로가기
Programming/Spring JPA

[자바 ORM 표준 JPA 프로그래밍] 영속성 관리

by 깐니 2022. 11. 27.

개요

JPA 영속성 컨텍스트에 대해 알아보자.

목차

  • 영속성 컨텍스트란?
  • 영속성 컨텍스트의 이점
  • Entity의 생명주기 (Entity LifeCycle)

영속성 컨텍스트란

정의

  • 엔티티를 영구 저장하는 환경
  • EntityManager로 접근 가능
  • 실제로 DB에 접근하기전에 영속성 컨텍스트 환경에 엔티티를 영속화 하겠다는 의미

(*영속화 : 참고 블로그 링크)

 

EntityManager와의 관계

  • 기본적으로 하나의 엔티티 매니저가 생성되면 1:1로 하나의 영속성 컨텍스트가 생성된다.
  • 하지만 스프링 프레임워크와 같은 컨테이너 환경의 JPA에서는 여러 엔티티 매니저가 하나의 영속성 컨텍스트를 공유하게 된다.


컨테이너 환경에서의 JPA

컨테이너를 사용하는 스프링과 같은 환경에서는 개발자가 EntityManager를 생성하지 않고 컨테이너가 생성 관리하게된다.

스프링은 싱글톤 기반으로 동작하기 때문에 속성값들은 모든 스레드가 공유하게 되는데 이때 발생하는 EntityManager의 thread-safe를 어떻게 보장하는 것인가?

  • EntityManager를 Proxy를 통해 감싸고 EntityManger를 사용할 때 Proxy를 통해 EntityManager를 생성한다.
  • EntityManager를 직접 사용하는 경우에는 @PersistenceContext를 사용하면 된다.



영속성 컨텍스트의 이점


1. 1차 캐시
영속성 컨텍스트 내부에는 Map<Key,Value> 형태로 저장되는 1차 캐시가 존재한다 (Key = id, Value = member entity)
1차 캐시로 반복 가능한 읽기 등급의 트랜잭션 수준을 디비가 아닌 애플리케이션 차원에서 제공한다.
트랜잭션이 종료되는 시점에 캐시도 모두 날라가기 때문에 애플리케이션이 작을 때는 큰 효과를 볼 수 없지만 비즈니스 로직이 복잡한 경우 성능 향상의 효과를 볼 수 있다.

Member member = new Member();
member.setId("memberID");
member.setPw("password");

// 트랜잭션 시작: 영속성 컨텍스트 생성
tx.begin();

// 영속성 컨텍스트에 저장
em.persist(member); 

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

DB에는 저장되어 있지만, 1차캐시에 데이터가 없는경우에는 어떻게 될까 ?
-> DB에서 조회해서 가져온 후, 1차 캐시에 저장하고 그 엔티티를 반환한다.


2. 동일성 보장(identity)

Member member1 = entityManager.find(Member.class, "member1");
Member member2 = entityManager.find(Member.class, "member1");
System.out.println(member1 == member2); // 동일성 비교 true

 


3. 트랜잭션을 지원하는 쓰기 지연 (Buffer: Transactional Write-behind)
member1을 캐시에 저장 후 쓰기 지연 SQL 저장소에 insert 쿼리를 저장한다.
member2도 동일하게 캐시에 저장후 insert 쿼리를 저장한다.
commit을 하는 시점에 영속성 컨텍스트 내 쓰기 지연 SQL 저장소에 보관된 쿼리문을 모두 DB에 보냄으로써(=flush()) 버퍼의 기능을 제공한다.

public void createMember() {
    EntityManager em = emf.createEntityManager();
    EntityTransaction tx = em.getTransaction();
    
    tx.begin();
    
    Member member1 = new Member("Id1", "pw1");
    Member member2 = new Member("Id2", "pw2");
    
    //두개의 Insert 쿼리문을 영속성 컨텍스트의 쓰기 지연 SQL 저장소에 쌓아둠
    em.persist(member1);
    em.persist(member2);
    
    //commit 하는 순간 DB에 쌓아둔 쿼리를 모두 보냄
    tx.commit();
}

 

 

4. 변경감지 : 더티 체킹(Dirty Checking)
Entity는 영속성 컨텍스트에 저장될 때 스냅샷을 생성한다.
수정하려는 객체를 조회하면 영속상태가되고 1차 캐시에 관리됨과 동시에 스냅샷이 생성된다.
(1) commit 시점에 엔티티와 스냅샷과 비교한다.
(2) 객체가 수정되었다면 별도의 update() 명령이 필요없이 UPDATE SQL을 생성하여 쓰기 지연 SQL 저장소에 저장한다.
(3) flush()가 발생하면 쓰기 지연 SQL 저장소의 SQL을 DB에 반영한다.

EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();

tx.begin();

// 영속 엔티티 조회: 1차 캐시 저장 && 스냅샷 생성 
Member memberA = em.find(Member.class, "memberA");

// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);

//커밋 전 영속성 컨텍스트에서 스냅샷과 비교하여 데이터 변경 감지 후 커밋 시 DB에 적용
tx.commit();

 

 

 

Entity 생명주기(Entity LifeCycle)

  • 비영속 (new / transient) : 객체를 생성한 상태, 영속성 컨텍스트와 전혀 관계가 없는 상태
Member member = new Member();
member.setId("memberID");
member.setPW("password");

 

  • 영속 (managed) : 트랜잭션을 시작하여 객체를 영속성 컨텍스트에 저장, 관리되고 있는 상태
Member member = new Member();
member.setId("memberID");
member.setPw("password");

EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();

tx.begin();

em.persist(member);

 

  • 준영속 (deteached) : 영속성 컨텍스트에 있다가 영속성 컨텍스트에서 지운 상태
detech(); // 더이상 영속성 컨텍스트에서 관리하지 않겠다고 선언

em.detach(entity) : 특정 엔티티만 준영속 상태로 전환

em.clear() : 영속성 컨텍스트를 완전히 초기화 

e,.close() : 영속성 컨텍스트를 종료

 

  • 삭제(removed) : 실제 DB 삭제를 요청한 상태
em.remove(member);