Skip to content

JPA 영속성 컨텍스트

YoungMinKim edited this page Jul 18, 2021 · 2 revisions

목차

  • 영속성 컨텍스트란?

이 강의를 통해 알고자 하는 부분

  • JPA에서 가장 중요한 영속성 컨텍스트에 대한 지식 습득
  • 1차 캐시와 쓰기 지연 저장소에 대한 부분 이해
  • JPQL, 플러시

TOOL

- `IDE` : Intellij
- `WAS` : Tomcat 9
- `DataBase` : H2
- `Build` : Maven

JPA에서 가장 중요한 2가지

  • 객체와 관계형 데이터베이스 매핑하기(Object Relational Mapping)
    • 객체와 관계형 데이터베이스를 어떻게 매핑할것인가?
    • DB를 어떻게 설계하고, 객체를 어떻게 설계해서 이거를 중간에서 JPA로 매핑해서 사용 할 것인가?
  • 영속성 컨텍스트
    • 실제 JPA가 내부에서 어떻게 동작 하는가?

영속성 컨텍스트

JPA를 이해하기 위해서는 영속성 컨텍스트를 이해해야 한다

  • JPA를 이해하는데 가장 중요한 용어
  • 엔티티를 영구 저장하는 환경이라는 뜻
    • 영속성 컨텍스트는 눈에 보이지 않는다.
    • 영속성 컨텍스트는 엔티티 매니저를 통해 접근한다.
  • EntityManager.persist(entity);
    • 여기서 persist 메서드는 DB에 저장하는게 아니라, 영속성 컨텍스트에 저장하는 것을 의미
  • (중요) 엔티티 매니저를 생성하면 그 안에 1:1로 영속성 컨텍스트가 생성이 된다.
    • 엔티티 매니저 안에 눈에 보이지 않는 공간이 생긴다 생각.

엔티티의 생명주기

  • 비영속 (new/transient)
    • 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
    //영속성 컨텍스트와 전혀 관계과 없는 새로운 상태
    Member member = new Member();
    member.setId(105L);
    member.setName("HelloJPA");
  • 영속 (managed)
    • 영속성 컨텍스트관리되는 상태
    Member member = new Member();
    member.setId(105L);
    member.setName("HelloJPA");
     
    //1. 영속성 컨텍스트에 관리되는 상태
    //2. em.persist() 호출시 영속성 컨텍스트의 1차 캐시와, 쓰기 지연 SQL 저장소에 해당 쿼리가 저장된다
    //3. Transaction commit시 쿼리가 날라간다 
    em.persist(member);
  • 준영속 (detached)
    • 영속성 컨텍스트에 저장되었다가 분리된 상태
    Member member = new Member();
    member.setId(105L);
    member.setName("HelloJPA");
     
    //영속성 컨텍스트에 관리되는 상태
    //em.persist() 호출시 영속성 컨텍스트의 1차 캐시와, 쓰기 지연 SQL 저장소에 해당 쿼리가 저장된다 
    em.persist(member);
    em.detached(member); //영속성 컨텍스트의 상태를 준영속으로 변경 및 분리
  • 삭제 (remove)
    • 삭제된 상태
      Member member = new Member();
      member.setId(105L);
      member.setName("HelloJPA");
       
      //영속성 컨텍스트에 관리되는 상태
      //em.persist() 호출시 영속성 컨텍스트의 1차 캐시와, 쓰기 지연 SQL 저장소에 해당 쿼리가 저장된다 
      em.persist(member);
      em.remove(member);

영속성 컨텍스트의 이점

  • 애플리케이션과 DB사이에 보이지 않는 어떤 부분이 존재한다?
    • 1차 캐시
    • 동일성 보장
    • 트랜잭션을 지원하는 쓰기 지연 (transaction write-behind)
    • 변경 감지 (Dirty Checking)
    • 지연 로딩 (Lazy Loading)

영속성 컨테스트-2

엔티티 조회, 1차 캐시

  • 영속성 컨텍스트는 내부에 1차 캐시를 가지고 있다.
//비영속 상태
Member member = new Member();
member.setId("member1");
member.setUserName("회원1");

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

//1차 캐시
+--------------------+
|@id          Entity |
|"member1"    member1|
|"member2"    member2|
|                    |
+--------------------+  
  • 조회를 하는경우
    • JPA는 우선 1차 캐시에 있는 값을 뒤진다.
      • em.find(Member.class, "member1");
      • 만약 member1 값이 영속성 컨텍스트의 1차 캐시 영역에 존재한다면, DB에 접근하지 않고 1차 캐시에서 값을 가져온다
    • 만약 1차 캐시에 해당 key값(member2)이 없을 시 DB 조회를 한 후에 해당 데이터를 member2의 Entity에 저장한다
      • 위 같은 상황을 통해 member2를 조회 시 DB가 아닌 1차 캐시에서 Entity를 조회한다
      • 위 상황은 1차 캐시에 @id member2가 없다는 가정하에 생각을 이어 나가야 한다
      • 1차 캐시는 하나의 트랜잭션이 끝나면 소멸, 찰나의 순간에서만 효과가 있다
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction(); // 트랜잭션을 얻는다
tx.begin(); // 트랜잭션 시작 -> DB 트랜잭션 시작

Member a = em.find(Member.class, "member1"); //여기서 가져온 Entity는 DB에서 가져온 값으로, 1차캐시 영역에 저장이 된다
Member b = em.find(Member.class, "member1"); //여기서 가져온 Entity는 DB가 아닌, 1차 캐시에서 값을 가져온다
  • 첫번째 라인은 DB에서 데이터를 가져온다.
  • 두번째 라인은 영속성 컨텍스트 안의 1차 캐시에서 해당 데이터를 가져온다.

영속 엔티티의 동일성 보장

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");

System.out.println(a == b); //동일성 비교 True
  • Java Collection에서 값을 꺼내 비교를 하면 True를 반환 하는 것처럼, JPA는 동일한 Entity의 동일성을 보장한다.

엔티티 등록

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

  • 영속성 컨텍스트 안에는 1차 캐시 뿐만 아니라, 쓰기 지연 SQL 저장소가 존재한다.
  • persist(memberA)를 넣으면 memberA가 1차 캐시로 들어가고 동시에 JPA가 해당 엔티티를 분석하여
    INSERT 쿼리를 생성한 후에 쓰기 지연 SQL 저장소에 쌓아둔다, 후에 tx.commit을 하지않고 persist(memberB)를 넣었다 가정해보자,
    그러면 다시 한번 JPA가 해당 엔티티를 분석하여 INSERT 쿼리 생성 후에 쓰기 지연 SQL 저장소에 쌓아둔다.
  • 언제 DB에 날라가는가? -> 트랜잭션 commit을 하면 쓰기 지연 SQL 저장소에 있는 얘들이 플러시가 되면서 DB에 적재 된다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction(); // 트랜잭션을 얻는다
tx.begin(); // 트랜잭션 시작 -> DB 트랜잭션 시작

em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 DB에 보내지 않는다

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

엔티티 수정

EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction(); // 트랜잭션을 얻는다
tx.begin(); // 트랜잭션 시작 -> DB 트랜잭션 시작

Member memberA = em.find(Member.class, 150L); //1차 캐시에 저장
memberA.setName("ZZZZZ"); //스냅샷 떠 둔 부분이 1차 캐시에 존재할거임

//em.persist(member) 이런 코드가 있어야 하지 않을까??

tx.commit();

+------------------------------------+
|@id          Entity    스냅샷         |
|"member1"    member1   memberA 스냅샷 |
|"member2"    member2   memberB 스냅샷 |
|                                    |
+------------------------------------+
  1. 값을 읽어온 최초의 상태(DB, 값을 넣은 상황)를 스냅샷으로 떠둔다
  2. JPATransaction Commit발생하면 **flush()**를 호출하게 되는데, 이때 해당 Entity최초로 생성된 Entity의 스냅샷을 비교 한다.
  3. MemberA가 변경된 것을 JPA가 감지 후 UPDATE SQL 생성 -> 쓰기 지연 SQL 저장소에 저장
  4. Commit시 해당 쿼리가 실행이 된다.

변경 감지

  1. JPA는 DB Transaction commit시 내부적으로 flush()가 호출 된다.
  2. 후에 들어온 Entitiy와 **스냅샷(Entity 최초 스냅샷)**을 비교한다.
  3. 변경 사항이 있으면 쓰기 지연 SQL 저장소에 쿼리를 저장해둔다.
  4. DB 반영

영속성 컨테스트-2

플러시 발생

영속성 컨텍스트의 변경 내용을 데이터베이스에 반영

  • 변경 감지
  • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
  • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송(등록, 수정, 삭제 쿼리)
  • 플러시는 영속성 컨텍스트를 비우지 않음
  • 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화
  • 트랜잭션이라는 작업 단위가 중요 -> 커밋 직전에만 동기화 하면됨

영속성 컨텍스트를 플러시하는 방법

  • em.flush() - 수동 호출 (즉시 쿼리가 날라간다)
  • 트랜잭션 커밋 - 플러시 자동 호출
  • JPQL 쿼리 실행 - 플러시 자동 호출

JPQL 쿼리 실행 시 플러시가 자동으로 호출되는 이유

em.persist(memberA);
em.persist(memberB);
em.persist(memberC);

//실제 쿼리는 안날라간다 여기서

//중간에 JPQL 실행
//flush가 안 날라가면 데이터 조회 시 예외가 발생할 것이다        
query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();
  • JPQL이 있을 시에는 **무조건 flush를 날린다
  • 위 같은 상황에서는 flush가 실행 되어야 하는 상황이다

플러시 모드 옵션

em.setFlushMode(FlushModeType.COMMIT)
  • FlushModeType.AUTO: 커밋이나 쿼리를 실핼할 때 플러시 (가급적 AUTO로 사용)
  • FlushModeTYpe.COMMIT: 커밋할 때만 플러시

준영속 상태

  • 영속 -> 준영속
  • 1차 캐시에 올라간 상태 -> 영속 상태
    • 조회를 했는데 1차 캐시에 없는 경우 해당 Entity를 1차 캐시에 적재
  • 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
  • 영속성 컨텍스트가 제공하는 기능을 사용 못함

준영속 상태 만드는 방법

  • em.detach(entity)
    • 특정 엔티티만 준영속 상태로 전환
  • em.clear()
    • 영속성 컨텍스트를 완전히 초기화
  • em.close()
    • 영속성 컨텍스트를 종료
Clone this wiki locally