[JPA] 영속성 컨텍스트

19·2022년 9월 18일
0

JPA

목록 보기
5/18
post-custom-banner

JPA에서 가장 중요한 2가지!

  • 객체와 관계형 데이터베이스 매핑하기
  • 영속성 컨텍스트

영속성 컨텍스트

영속성 컨텍스트를 이해하면 JPA 내부 동작 방식을 알 수 있다.

  • JPA를 사용하면, EntityManagerFactory를 통해서 고객 요청시마다 EntityManager를 생성한다.
  • EntityManager는 내부적으로 커넥션풀에서 커넥션을 사용해서 DB와 통신한다.

영속성 컨텍스트는 엔티티를 영구 저장하는 환경이라는 뜻!
영속성 컨텍스트는 논리적 개념이며, EntityManager를 통해 영속성 컨텍스트에 접근한다.

EntityManager.persist(entity);
  • DB에 바로 저장하는 것이 아니라, 영속성 컨텍스트에 저장한다.

엔티티 생명주기


비영속 (new/transient)

// 객체만 생성된 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
  • 최초의 객체가 생성된 상태를 의미한다.
  • 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
  • JPA와 아무 관련없는 상태

영속 (managed)

// 객체만 생성된 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

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

// 객체를 저장한 상태 (영속)
em.persist(member);
  • persist()를 통해 객체를 저장하면 영속성 컨텍스트에 관리되는 상태가 된다.
  • DB에 바로 저장되는 것이 아니라, 영속성 컨텍스트에 저장된다.
  • 트랜잭션 commit이 되어야 영속성 컨텍스트에 저장된 객체를 DB로 저장하는 쿼리를 날린다.

준영속 (detached)

// 영속성 컨텍스트에 저장된 객체를 분리, 준영속 상태
em.detach(member);
  • 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 영속성 컨텍스크가 제공하는 기능을 사용할 수 없는 상태
    • JPA가 관리하지 않는다

삭제 (removed)

// 객체를 삭제한 상태 (삭제)
em.remove(member);
  • DB에서 삭제

영속성 컨텍스트의 이점?

영속성 컨텍스트의 사용으로 얻을 수 있는 5가지 이점이 있다.


1차 캐시

영속성 컨텍스트 내부에 1차 캐시라는 것이 존재한다.

// 객체만 생성된 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

// 객체를 저장한 상태 (영속)
em.persist(member);
  • persist를 통해 객체를 영속화한다.
  • 1차 캐시에 저장된다.
    • 1차 캐시를 영속성 컨텍스트로 이해해도 좋다.

영속성 컨텍스트에 저장된 객체를 가져올 땐(조회) 어떤 일이 벌어질까?

영속성 컨텍스트에 저장된 객체를 가져올 땐 바로 DB를 조회하지 않고, 1차 캐시에 해당 객체가 있는지 먼저 조회해보고, 없다면 DB에서 찾는다


1차 캐시에 저장된 객체를 조회하는 경우)

// 객체만 생성된 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

// 객체를 저장한 상태 (영속), 1차 캐시에 저장
em.persist(member);

// 1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
  • 영속성 컨텍스트에서 먼저 찾고, 없으면 DB에서 찾는다.

1차 캐시에 저장되지 않은 객체를 조회하는 경우)

// 1차 캐시에 없으므로, DB에서 조회한다.
Member findMember = em.find(Member.class, "member2");
  • 1차 캐시에 저장되지 않은 객체는 JPA가 DB에서 조회해 1차 캐시에 저장한다.
  • 1차 캐시에 저장한 객체를 반환해준다.

동일성(identity) 보장

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

// 동일성 비교
System.out.println(a == b);
  • true가 출력된다.

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

EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction()
tx.begin();  // 트랜잭션 시작

// 영속, 1차 캐시에 저장
em.persist(memberA);
em.persist(memberB);

// 객체가 1차 캐시에 저장된 상태, 아직 DB에 INSERT 쿼리 날리지 않음

// 트랜잭션 커밋, 커밋하는 순간 DB에 INSERT 쿼리를 날린다.
tx.commit();  
  • persist()로 객체를 영속화하면, 1차 캐시에 저장된다.
  • 동시에, 쓰기 지연 SQL 저장소에 INSERT 쿼리가 쌓인다.
    • commit()을 통해 커밋하면, DB에 INSERT 쿼리를 날린다.

em.persist(memberA); 했을 때 일어나는 일)


em.persist(memberB); 했을 때 일어나는 일)


tx.commit(); 했을 때 일어나는 일)

왜 굳이 이런식으로?

persist()시 마다 쿼리가 날라가면, 최적화 할 수 있는 여지자체가 없기 때문이다.
쓰기 지연 SQL 저장소에 쿼리들을 모아뒀다가 한꺼번에 DB로 날릴 수 있는 장점이 있다.
(최적화, 효율)


변경 감지 (Dirty Checking)

EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction()
tx.begin();  // 트랜잭션 시작

// 영속된 객체 조회
Member memberA = em.find(Member.class, "memberA");

// 영속된 객체의 데이터 수정
memberA.setUsername("hello");
memberA.setAge(20);

// 데이터 수정 후, 다시 저장해야하지 않을까?
// em.persist(memberA);


// 트랜잭션 커밋, 커밋하는 순간 DB에 UPDATE 쿼리를 날린다.
tx.commit();  
  • JPA의 목적은 객체를 자바 컬렉션 다루듯 하는 것
  • 1차 캐시에서 가져온 객체의 데이터를 수정했다면, 다시 저장하지 않아도 된다
    • 수정된 부분을 저장하지 않았지만, JPA가 변경을 감지하고 UPDATE 쿼리를 날린다.

  • 1차 캐시에 객체를 저장한 시점(DB에서 값을 읽어온 시점, persist()한 시점)을 스냅샷에 저장해둔다.
  • commit을 할 때, 객체와 스냅샷을 비교해 변경된 부분이 있다면 UPDATE 쿼리를 날린다.

지연 로딩 (Lazy Loading)


플러시 (Flush)

영속성 컨텍스트의 변경내용을 DB에 반영하는 것이다.
트랜잭션 커밋 시, 자동으로 플러시가 발생한다.

플러시 이후에 영속성 컨텍스트가 비워지는 것이 아니다.
영속성 컨텍스트는 그대로 유지된다.
commit 직전에만 DB와 동기화하면 된다.


플러시가 발생하면 생기는 일?

  • 변경 감지
  • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
  • 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송
    (INSERT, UPDATE, DELETE 쿼리)

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

em.flush()

플러시를 직접 호출한다.

Member member = new Member(1L, "member1");
em.persist(member);

em.flush();
System.out.println("================");
tx.commit();
  • 커밋되는 시점 이전에 flush를 강제로 수행했다.
  • 변경감지를 수행하고, 생성된 쿼리를 바로 DB로 날려 적용한다.

  • 커밋되는 시점 이전에 플러시가 수행된 것을 확인할 수 있다.

트랜잭션 커밋

플러시 자동 호출


JPQL 쿼리 실행

플러시 자동 호출



참고

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 김영한

profile
하나씩 차근차근
post-custom-banner

0개의 댓글