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 저장소가 존재.
  1. persist(memberA)를 넣으면 memberA가 1차 캐시로 들어가고 동시에 JPA가 해당 엔티티를 분석.
  2. INSERT 쿼리를 생성 후 쓰기 지연 SQL 저장소에 저장해둔다. 
  3. 하나의 트랜젝션 내에서 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()**를 호출한다.
  3. 해당 Entity최초로 생성된 Entity의 스냅샷을 비교 한다.
  4. MemberA가 변경된 것을 JPA가 감지 후 UPDATE SQL 생성 -> 쓰기 지연 SQL 저장소에 저장
  5. 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