[JPA] 영속성(Persistence) 관리와 영속성 컨텍스트(Persistence Context)

 

지난번에는 JPA와 관련하여 기본적인 세팅과 EntitiyManagerFactory, EntitiyManager, EntitiyTransaction의 특징과 어떤 역할을 하는지 알아봤고 이번에는 본격적으로 JPA를 사용해보려고 한다.

 

 

2021.08.17 - [Spring-Boot/JPA] - [JPA] JPA? JPA 세팅 및 시작하기

 

[JPA] JPA? JPA 세팅 및 시작하기

[JPA] JPA? JPA 세팅 및 시작하기 기존에 MyBatis 방식만을 사용하다가 ORM(Object-Relation Mapping)기반인 JPA(Java Persistence Interface) 프로그래밍에 관심이 생겨서 알아보게 되었다. 자바 퍼시스턴스 API..

dbsyys.tistory.com

 

 

JPA에는 영속성 컨텍스트(Persistence Context) 라는 용어가 등장한다. 영속성 컨텍스트란 엔티티를 영구 저장하는 환경을 의미한다. 관계형 데이터베이스(RDB)는 데이터나 데이터의 집합을 테이블에 저장, 관리하며  객체지향 프로그래밍에서는 그 테이블을 엔티티라고한다.

 

영속성 컨텍스트는 논리적인 개념이며, 우리가 앞에서 선언했던 EntitiyManager를 통해서 영속성 컨텍스트에 접근이 가능하다. EntityManager에게 관리되는 엔티티는 생명주기를 가지게 되는데 영속성 컨텍스트를 기준으로 아래의 상태를 가진다.

- 비영속(new/transient)

  • 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태

- 영속(managed)

  • 영속성 컨텍스트에 관리되는 상태

- 준영속(detached)

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

- 삭제(removed)

  • 삭제된 상태

 

 

각각의 상태가 영속성 컨텍스트에서 어떻게 관리되는지 알아보겠다.


import javax.persistence.Entity; 
import javax.persistence.Id; 

@Entity 
@Getter 
@Setter
public class Member { 
	
	@Id 
	private String id; 
	private String name; 
 
}

Member 엔티티를 생성했다.

 

public class JpaTest {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
        	// 객체만 생성한 상태(비영속)
            Member member = new Member();
            member.setId("memberId");
            member.setName("memberName");
			
            // 객체를 저장한 상태(영속)
            em.persist(member);
            
            // 엔티티를 영속성 컨텍스트에서 분리(준영속)
            em.detach(member);
            
            // 객체를 삭제한 상태(삭제)
            em.remove(member);
            
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
        emf.close();

    }
}

 

엔티티 생명주기별 각각의 상태.

 

JPA에서는 영속성 컨텍스트에 관하여 각각의 상태를 오가며, 영속성 컨텍스트에 관리되는 영속 상태인 엔티티에서 저장, 수정, 조회, 삭제 처리한다.

 

그렇다면 왜 ? 굳이

 

비영속, 영속, 준영속, 삭제의 4가지 상태로 구분되며, 영속성 컨텍스트는 애플리케이션과 데이터베이스 사이에서 데이터를 어떻게 관리하고 처리하는지에 대해 알아보겠다.

 


 

영속성 컨텍스트의 특징으로 아래 항목이 있다.

  • 1차 캐시
  • 동일성(identity)
  • 트랜잭션을 지원하는 쓰기 지연(transactional write-begind)
  • 변경 감지(Dirty Checking)
  • 지연 로딩(Lazy Loading)

 

- 1차 캐시

1차 캐시는 엔티티가 영속성 컨텍스트에 저장되어 영속 상태로 된다면 key와 value 형태로 1차 캐시라는 곳에 저장이 된다. key는 엔티티에서 @Id로 지정한 pk값 (=memberId), value는 해당 엔티티 자체가 된다(=Member).

Member member = new Member();
member.setId("memberId");
member.setName("memberName");

// 영속 상태 (1차 캐시에 저장됨)
em.persist(member);

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

member 객체가 1차 캐시에 저장된 상태에서 JPA는 member 객체를 조회하면 데이터베이스에 접근해서 데이터를 가져오는 것이 아닌 1차 캐시를 먼저 확인 한 후에 해당하는 데이터가 없다면 그 때 데이터베이스를 조회한다.

 

만약 memberId2라는 키를 가진 데이터가 데이터베이스에선 존재하고 영속성 컨텍스트의 1차 캐시 안에는 없다고 가정하고 조회를 해보면

Member findMember2 = em.find(Member.class, "memberId2");

영속성 컨텍스트는 데이터베이스에서 해당하는 데이터를 가져온 후에 그 값을 1차 캐시에 저장하고 반환한다.

 

 

- 동일성(identity)

JPA는 같은 트랜잭션 안에서 1차 캐시를 통해 데이터베이스가 아닌 애플리케이션 차원으로 영속된 엔티티의 동일성을 보장해준다.

Member member1 = em.find(Member.class, "memberId");
Member member2 = em.find(Member.class, "memberId");

System.out.println(member1 == member2); // true

 

 

- 트랜잭션을 지원하는 쓰기 지연(transactional write-begind)

JPA는 영속성컨텍스트의 트랜잭션안에서 데이터가 변경된다. member1과 member2를 차례대로 저장 할 때 1차 캐시에 엔티티의 정보가 저장됨과 동시에 저장하는 insert 쿼리가 생성되지만 영속성 컨텍스트 안에만 저장되고 데이터베이스로 쿼리가 전송되지 않는다.

EntityManager em = emf.createEntityManager();

EntityTransaction transaction = em.getTransaction();

// 데이터 변경 시
transaction.begin(); // 트랜잭션 시작

em.persist(member1);
em.persist(member2);
// 영속성 컨텍스트에 등록 관리 -> query를 보내지 않음

// 커밋하는 순간 데이터베이스에 query를 보냄
transaction.commit(); // 트랜잭션 커밋

영속성 컨텍스트에 저장 되어있는 쿼리는 트랜잭션에서 commit()이라는 메서드가 실행되어야 비로소 데이터베이스로 저장 되어있던 쿼리가 보내진다.

 

 

 

- 변경 감지(Dirty Checking)

영속성 컨텍스트는 최초의 데이터 조회나 저장할 때 1차 캐시에 @Id로 지정한 pk값과 엔티티말고도 엔티티에 관련된 스냅샷을 저장한다. 스냅샷에는 최초 데이터가 조회 또는 저장된 시간이나 그 시점의 데이터가 저장되는데,  JPA에서 트랜잭션이 commit() 하는 시점에 엔티티와 스냅샷을 비교한다.

EntityManager em = emf.createEntityManager();

EntityTransaction transaction = em.getTransaction();

transaction.begin(); // 트랜잭션 시작

// 데이터 조회(영속)
Member member1 = em.find(Member.class, "memberId");

// 영속된 엔티티 데이터 수정
member1.setId("memberIdTest");
member1.setName("memberNameTest");

transaction.commit(); // 트랜잭션 커밋

비교 후에 기존 값과 스냅샷의 데이터가 다르면 영속성 컨텍스트에 update 쿼리를 만들어 놓게되고 flush라는 작업을 거치면서 데이터베이스에 반영되고 데이터베이스 commit가 실행된다. 이러한 매커니즘으로 데이터 수정 시에는 저장(persist), 조회(find), 삭제(remove)와 같은 처리가 따로 필요없다.

 

*flush

플러시는 영속성 컨텍스트에 변경된 내용을 데이터베이스에 반영(동기화)하는 것이다. 플러시가 발생하면 변경감지와 쓰기 지연 SQL 저장소에 수정된 엔티티가 등록되고 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 등록한다.(저장, 수정, 삭제)

 

플러시 후에 데이터베이스 트랜잭션 커밋이 일어나는 것은 아니다. 데이터베이스 트랜잭션 커밋은 JPA의 트랜잭션에서 commit()을 실행했을 때 일어난다.

 

flush를 수행했을 시 쓰기 지연 SQL저장소에 있거나 변경감지로 인해 만들어진 쿼리들이 데이터베이스에 반영이 되지만 1차 캐시는 그대로 남아있고 사라지지 않는다.


'JPA' 카테고리의 다른 글

[JPA] JPA 엔티티 매핑과(Entity Mapping) 엔티티 설계  (1) 2021.08.31
[JPA] JPA? JPA 세팅 및 시작하기  (1) 2021.08.17

+ Recent posts