본문 바로가기
JPA/JPA

[JPA] 영속성 컨텍스트 (Persistence context)

by 걸어가는 신사 2021. 12. 29.
엔티티를 영구 저장하는 환경
  • 엔티티 매니저(Entity Manager)로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.
  • em.persist(member);
    • persist() 메소드는 엔티티 매니저를 사용해서 회원 엔티티를 영속성 컨택스트에 저장한다.
  • 영속성 컨텍스트는 논리적인 개념이다.

  • 영속성 컨텍스트는 엔티티 매니저를 생성할 때마다 하나씩 만들어진다.
  • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근할 수 있고, 영속성 컨텍스트를 관리할 수 있다.

 

1. 엔티티의 생명 주기

(1) 비영속(new/transient)

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

Member member = new Member();
member.setId(1L);
member.setName("MemberA");
  • 엔티티 객체를 생성
  • 순수한 객체 상태이며 아직 정장하지 않았다.
  • 영속성 컨텍스트나 데이터베이스와는 전혀 관련이 없다.

(2) 영속(managed)

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

em.persist(member);
Member finMember = em.find(Member.class, 1L);
List<Member> result = em.createQuery("select m from Member as m", Member.class).getResultList();
  • 엔티티 매니저를 통해서 엔티티를 영속성 컨텍스트에 저장
  • 영속성 컨텍스트가 관리하는 엔티티를 영속 상태라 한다.
  • em.find()나 JPQL을 사용해서 조회한 엔티티도 영속성 컨텍스트가 관리하는 영속 상태이다.

(3) 준영속(detached)

영속성 컨텍스트에 저장되었다가 분리된 상태
em.detach(member); // 영속성 컨텍스트에서 분리
em.clear(); // 영속성 컨텍스트 초기화
em.close(); // 영속성 컨텍스트 닫기
  • 영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않으면 준영속 상태가 된다.
  • 영속성 컨텍스트가 제공하는 기능을 사용하지 못한다.
  • 위 3가지 방법으로 준영속 상태를 만들 수 있다.

(4) 삭제(removed)

삭제된 상태
em.remove(member); // 객체 삭제
  • 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제한다.

 

2. 영속성 컨텍스트의 장점

(1) 1차 캐시

//비영속
Member member = new Member();
member.setId(1L);
member.setName("MemberA");
//영속
em.persist(member);
  • 영속성 컨텍스트는 내부에 캐시를 가지고 있는데 이것은 1차 캐시라 한다.
  • 영속 상태의 엔티티는 모두 이곳에 저장된다.
  • 영속성 컨텍스트 내부에 Map이 하나 있다고 생각하면 된다.
    • Key : @Id로 매핑한 식별자(데이터베이스의 기본키)
    • Value : 엔티티 인스턴스
  • 영속성 컨텍스트에 데이터를 저장하고 조회하는 모든 기준은 데이터베이스 기본 키 값이다.
    • EntityManger.find() 메서드 정의
    • public <T> T find(Class<T> entityClass, Object primaryKey);

* 1차 캐시에서 조회

1차 캐시에서 조회

//1차 캐시에서 조회
Member member = new Member();
member.setId(1L);
member.setName("MemberA");

//1차 캐시에 저장된다.
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, 1L);
  • em.find()를 호추하면 우선 1차 캐시에서 식별자 값으로 엔티티를 찾는다.
  • 찾는 엔티티가 있으면 데이터베이스를 조회하지 않고 메모리에 있는 1차 캐시에서 엔티티를 조회한다.

*데이터베이스에서 조회

데이터베이스에서 조회

//데이터베이스에서 조회
Member findMember2 = em.find(Member.class, 2L);
  • em.find()를 호출했는데 엔티티가 1차 캐시에 없으면 엔티티 매니저는 데이터베이스를 조회해서 엔티티를 생성한다.
  • 1차 캐시에 저장한 후 영속상태의 엔티티를 반환한다.

(2) 영속 엔티티의 동일성 보장

//영속 엔티티의 동일성 보장
Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101l);
System.out.println(findMember2==findMember1);  //true
  • em.find()를 반복해서 호출하는 경우 영속성 컨텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스를 반환한다.
  • 따라서 둘은 같은 인스턴스이므로 true이다.
  • 영속성 컨텍스트는 엔티티의 동일성을 보장한다. 

(3) 트랜잭션을 지원하는 쓰기 지연(Transactional write-behind)

EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
//엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 한다.
tx.begin(); //트랜잭션 시작

Member member1 = new Member(150L, "A");
Member member2 = new Member(160L, "B");

em.persist(member1);
em.persist(member2);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.

tx.commit(); //커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.

em.persist(memberA) 
em.persist(memberB), tx.commit()

  • 엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쓰기 지연 SQL 저장소에 INSERT SQL을 모아둔다.
  • 트랜잭션을 커밋하면 엔티티 매니저는 영속성 컨텍스트를 플러시 한다.
    • 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업
    • 플러시를 통해 등록, 수정, 삭제한 엔티티를 데이터베이스에 반영한다.

(4) 변경 감지(Dirty Checking)

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

Member member = em.find(Member.class, "memberA");

//영속 엔티티 데이터 수정
member.setName("JPA");

tx.commit();
  • JPA로 엔티티를 수정할 때는 단순히 엔티티를 조회해서 데이터만 변경하면 된다.
  • 트랜잭션 커밋 직전에 주석으로 처리된 em.update(member) 메서드를 실행해야 할 것 같지만 이런 메서드는 없다.

변경 감지(Dirty Checking)

  •  트랜잭션을 커밋하면 엔티티 매니저 내부에서 먼저 플러시가 호출된다.
  • 엔티티와 스냅샷을 비교해서 변경된 엔티티를 찾는다.
  • 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보낸다.
  • 쓰기 지연 저장소의 SQL을 데이터베이스에 보낸다.
  • 데이터베이스 트랜잭션을 커밋한다.

 

4. 플러시 (Flush)

영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.
  • 영속성 컨텍스트를 비우지 않는다.
  • 영속성 컨테스트의 변경내용을 데이터베이스에 동기화

(1) 플러시 실행 시 동작

  • 변경 감지가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해서 수정된 엔티티를 찾는다.
    • 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록한다.
  • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송한다.

(2) 영속성 컨텍스를 플러시하는 방법

  • em.flush() 직접 호출
  • 트랜잭션 커밋 시 플러시 자동 호출
  • JPQL 쿼리 실행 시 플러시 자동 호출

* 식별자를 기준으로 조회하는 find() 메서드를 호출할 때는 플러시가 실행되지 않는다.

Member member1 = new Member(300L, "JPA");
em.persist(member1);
Member member = em.find(Member.class, 1L);
System.out.println("================");
tx.commit();

 

em.find() 호출 시에 select query는 즉시 나간다.

하지만 이전에 영속성 컨텍스트에 등록되어 있던 member1은 플러시 되지 않는다.

이후 커밋할 때 데이터베이스에 반영된다.

 

 

 

 

 

 

 

 

반응형

'JPA > JPA' 카테고리의 다른 글

[JPA] 상속 관계 매핑  (0) 2022.01.03
[JPA] 연관관계 매핑 - 2  (0) 2022.01.02
[JPA] 연관관계 매핑 - 1  (0) 2022.01.01
[JPA] 엔티티 매핑 (Entity Mapping)  (0) 2021.12.31
JPA 사용하기 (feat. Maven)  (0) 2021.12.28

댓글