김영한님이 지음, [자바 ORM 표준 JPA 프로그래밍] 책을 읽고 정리한 필기입니다.📢

영속성 컨텍스트란?

JPA를 이해하는데 가장 중요한 용어 : 영속성 컨텍스트(persistence context) == "엔티티를 영구 저장하는 환경"

  • 엔티티 매니저로 엔티티를 저장하거나 조회 시 → 영속성 컨텍스트에 엔티티를 보관하고 관리

    1
    
      em.persist(member); //이 코드를 정확히 이야기 한다면 "엔티티 매니저를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장 한다." 이다.
    
  • 눈에 보이지 않는 논리적인 개념 공간
  • 엔티티 매니저를 생성할 때 하나 만들어 짐
  • 여러 엔티티 매니저가 같은 영속성 컨텍스트에 접근 가능

영속성 컨텍스트의 특징

  • 영속성 컨텍스트와 식별자 값
    • 엔티티는 식별자 값(@Id로 테이블의 기본 키와 매핑한 값)으로 구분
      • 영속 상태는 식별자 값이 반드시 있어야 함 (없을 경우 예외)
  • 영속성 컨텍스트와 데이터베이스 저장
    • 영속성 컨텍스트에 저장된 엔티티가 데이터베이스에 저장되는 시점?
      • 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영 함 == 이것을 플러쉬(flush())라 함
  • 영속성 컨텍스트가 엔티티를 관리하면서 발생되는 장점
    1. 1차 캐시
    2. 동일성 보장
    3. 트랜잭션을 지원하는 쓰기 지연
    4. 변경 감지
    5. 지연 로딩

엔티티 조회 매커니즘

영속성 컨텍스트는 내부에 캐시를 가지고 있는데 이것을 1차 캐시라 함, 영속 상태의 엔티티는 모두 이곳에 저장 됨

=> @Id를 키로하고 값을 Entity 인스턴스로 하는 Map

1
2
3
4
5
6
7
//엔티티를 생성한 상태(비영속)
Memeber member = new Member();
member.setId("member1");
member.setUsername("회원1");

//엔티티를 영속
em.persist(member);

위 코드가 실행되면 1차 캐시에 엔티티가 저장되고 아직 데이터베이스엔 저장되지 않는다.

img-name
영속성 컨텍스트 내부 1차 캐시

1차 캐시의 키는 식별자 값이며, 식별자 값은 데이터베이스 기본 키와 매핑되어 있다. 따라서, 영속성 컨텍스트에 데이터를 저장하고 조회하는 모든 기준은 데이터베이스 기본 키 값이다.

1차 캐시에 조회

em.find() 를 호출하면 1차 캐시에서 식별자 값으로 조회한 후 엔티티가 존재한다면 메모리에 있는 1차 캐시에서 엔티티를 조회, 없다면 데이터베이스에서 조회한다.

img-name
1차 캐시에서 조회

1
2
3
4
5
6
7
8
9
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

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

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

데이터베이스에서 조회

1차 캐시에 엔티티가 존재하지 않을 경우?

  • 엔티티 매니저는 데이터 베이스를 조회해서 엔티티를 생성, 그 후 1차 캐시에 저장하여 영속 상태의 엔티티를 반환
1
Member findMember2 = em.find(Member.class, "member2");

img-name
1차 캐시에 없을 경우

member1, member2엔티티 인스턴스는 1차 캐시에 존재하게 되어 성능상 이점을 누릴 수 있게된다.

영속 엔티티의 동일성 보장

1
2
3
4
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member2");

System.out.println(a == b); //동일성 비교, 결과는 참

영속성 컨텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스의 ‘참조’를 반환하기 때문에 둘은 같은 인스턴스이다.

💡 Tip. 동일성과 동등성

  • 동일성(identity) : 실제 인스턴스가 같다. 따라서 참조 값을 비교하는 == 비교의 값이 같다.
  • 동등성(equality) : 실제 인스턴스는 다를 수 있지만 인스턴스가 가지고 있는 값이 같다. 자바에서 동등성 비교는 equals() 메소드를 구현해야 한다.

JPA는 1차 캐시를 통해 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공한다는 장점이 있다.

엔티티 저장 매커니즘

1
2
3
4
5
6
7
8
9
10
11
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 한다.
transaction.begin(); //[트랜잭션] 시작

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

//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); //[트랜잭션] 커밋
  • 엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않음
  • 커밋 전까지 INSERT SQL을 모아 둔 뒤 커밋할 때 모아둔 쿼리를 데이터베이스에 전달 함 => 이것을 쓰기 지연이라 함

img-name
쓰기 지연 매커니즘, member A 영속

img-name
쓰기 지연 매커니즘, member B 영속

img-name
쓰기 지연 매커니즘, 커밋

트랜잭션을 커밋하면 엔티티 매니저는 우선 영속성 컨텍스트를 플러시한 뒤 실제 데이터베이스에 트랜잭션을 커밋한다.

  • 플러시(flush()): 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화 하는 작업

엔티티 변경 매커니즘

엔티티의 변경사항을 데이터베이스에 자동으로 반영하는 기능을 변경 감지(dirty checking)라 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();  //[트랜잭션] 시작

//영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");

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

//em.update(member) 이런 코드가 있어야 하지 않을까?
transaction.commit(); //[트랜잭션] 커밋

img-name
변경 감지 매커니즘

  • JPA는 엔티티를 영속성 컨텐스트에 보관할 때 최초 상태를 복사해서 스냅샷을 저장해 둔다.
  • 플러시 시점에 스탭샷과 엔티티를 비교하여 변경된 엔티티를 찾는다.
  • 변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용 된다. (비영속, 준영속 상태의 엔티티에는 적용 X)
  • JPA의 기본 전략은 엔티티의 모든 필드를 업데이트하는 것이다. 데이터베이스에 보내는 데이터 전송량이 증가하는 단점 존재
    • Why? 수정 쿼리가 항상 같기 때문에 애플리케이션 로딩 시점에 수정 쿼리를 미리 생성하여 재사용 가능
    • 동일한 쿼리를 데이터베이스에 보내면 데이터베이스는 이전에 한 번 파싱된 쿼리로 재사용 가능
  • 필드가 많거나 저장되는 내용이 너무 크면 동적으로 UPDATE SQL을 생성하는 전략을 선택 가능(하이버네이트 확장 기능 사용[@DynamicUpdate])

엔티티 삭제 매커니즘

1
2
Member memberA = em.find(Member.class, "memberA"); //삭제 대상 엔티티 조회
em.remove(memberA); //엔티티 삭제

엔티티 저장 매커니즘과 동일하게 삭제 쿼리를 쓰기 지연 SQL 저장소에 등록하고 이후 트랜잭션을 커밋해서 플러시를 호출하면 실제 데이터베이스에 삭제 쿼리를 전달한다.

em.remove(memberA);를 호출하는 순간 memberA는 영속성 컨텍스트에서 제거된다. 이렇게 삭제된 엔티티는 재사용하지 말고 자연스럽게 가비지 컬렉션의 대상이 되도록 두는 것이 좋다.

JPA 카테고리 내 다른 글 보러가기

댓글남기기