⬛️ [JPA] 영속성 컨텍스트

이준영·2023년 9월 16일
0

⬛️ JPA

목록 보기
3/3
post-thumbnail
참고

개인적으로 영한님 강의를 본 후 정리하며 복습하기 위한 글 입니다.


JPA 에서 가장 중요한 2가지를 뽑으라면 객체와 관계형 데이터베이스를 매핑하는것 과 영속성 컨텍스트 두가지가 있습니다.

이 중에 영속성 컨텍스트에 대해 정리해 보려 합니다.


영속성 컨텍스트 (Persistence Context)

영속성 컨텍스트 ?

영속성 컨텍스트는 JPA를 이해하는데 가장 중요한 언어로, 엔티티를 영구 저장하는 환경 이라는 뜻을 가집니다.

또한 영속성 컨텍스트는 논리적인 개념으로 엔티티 매니저를 통해 영속성 컨텍스트에 접근 가능합니다.


영속성 컨텍스트의 이점

  1. 1차 캐시
  2. 동일성 보장
  3. 쓰기 지연
  4. 변경감지
  5. 지연로딩 - 이후에 배움

em.persist(entity) 의 실제 동작 원리

Entity의 생명주기
  • 비영속 new/transient
  • 영속 managed
  • 준영속 detached
  • 삭제 removed

em.persist를 호출하면 그 즉시 db에 insert 쿼리가 날라갈 것으로 예상하고 있었지만 실제로는 그렇지 않습니다. db에 쿼리가 날라가는것은 트랜잭션 커밋이 이루어지는 순간에 일어나고, em.persist는 해당 엔티티를 비영속 상태에서 영속상태로 만들어 영속성 컨텍스트에 저장 한다는 의미입니다.

// 비영속
Member member = new Member();
member.setId(101L);
member.setName("hello");

// 영속
System.out.println("===== BEFORE =====");
em.persist(member); // member를 영속성 컨텍스트에 저장
System.out.println("===== AFTER =====");

tx.commit(); // flush()가 일어나면서 insert 쿼리문 날아감

위 코드를 실행할 경우 BEFORE와 AFTER 사이에 insert쿼리가 출력되지 않고 이후 트랜잭션 커밋 시점에 SQL 쿼리문이 날아가는걸 볼 수 있습니다.

출력결과


1차 캐시

영속성 컨텍스트 내부에는 1차 캐시가 존재합니다.

em.find()로 조회를 하게되면 영속성 컨텍스트의 1차캐시에서 먼저 조회하고, 존재하지 않는다면 db에서 조회해서 가져오게 됩니다. db에서 값을 조회해 올 경우 1차 캐시에 존재하지 않기 때문에 조회해 온 값도 1차 캐시에 저장하게 됩니다.

Member member = new Member();
member.setId(221L);
member.setName("hello");

em.persist(member); // member 엔티티를 영속화 시키며 1차 캐시에 저장
System.out.println("========조회========");
em.find(Member.class, member); // 1차 캐시에서 가져오기 때문에 db에 조회쿼리 날리지 않음
출력결과


TMI 정보
영속성 컨텍스트는 보통 트랜잭션과 생명주기를 같이합니다. 고객의 요청이 들어와서 비즈니스가 끝나면 1차 캐시도 다 날아가기 때문에 성능적인 이점을 크게 가져와 주진 않습니다. 그러나 1차 캐시가 제공하는 기능적인 부분들로부터 많은 도움을 받을 수 있습니다.


영속성 엔티티의 동일성 보장

// 똑같은 엔티티를 두번 조회해서 equals() 비교 
Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L);

System.out.println(findMember1 == findMember2);

출력 결과를 확인해 보면 첫번째 em.find를 호출할 때는 1차캐시에 조회하려는 엔티티가 존재하지 않아 SQL 쿼리가 날아갔지만, 두번째 조회에서는 1차캐시에서 가져왔기에 쿼리문이 총 1개만 날아간걸 확인 가능합니다.

또한 영속성 컨텍스트에서 조회시 컬렉션에서 같은 값을 꺼내는 것 같이 동일한 인스턴스를 반환 받을 수 있는 것을 확인 가능합니다.

출력 결과



쓰기 지연

영속성 컨텍스트 내부에는 쓰기 지연 SQL 저장소 라는 것도 존재합니다. em.persist로 엔티티를 영속화 시킨다면 insert SQL이 쓰기 지연 저장소에 쌓이게 되고 실제 SQL 쿼리는 트랜잭션이 커밋할 때 flush가 일어나며 그때 전부 날아가게 됩니다. (버퍼링 기능)

Member member1 = new Member(150L, "A");
Member member2 = new Member(160L, "B");
em.persist(member1);
em.persist(member2);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.

System.out.println("=================");

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



변경 감지 (Dirty Checking)

컬렉션에서 조회한 값을 변경하면 해당 객체의 값이 변경되는 것처럼 JPA 에서 수정의 경우 객체를 조회하고 해당 값을 변경하게 된다면 커밋 시점에 업데이트 쿼리가 db로 날아가게 됩니다.

Member member = em.find(Member.class, 150L);
member.setName("ZZZZZ");
System.out.println("=================");
tx.commit();
출력 결과

이러한 특이한 현상이 일어나는 이유는 영속성 컨텍스트의 변경 감지 기능 덕분 입니다. 1차 캐시 내부에는 처음 캐시에 올라올때의 상태를 저장해놓은 스냅샷이 존재합니다. 커밋을 하는 시점에 내부적으로 flush가 호출되고, 1차 캐시 내부의 엔티티와 스냅샷을 비교합니다. 그리고 변경 사항이 있다면 Update 쿼리를 쓰기 지연 SQL 저장소에 추가 한 후, 1차 캐시 모든 객체의 비교가 끝나면 쿼리를 한번에 날리면서 수정이 이루어집니다.

💡 삭제도 동일한 방식으로 이루어집니다! 커밋 시점에 delete 쿼리가 나가게 됩니다.


flush

플러시는 영속성 컨텍스트의 변경사항을 데이터베이스에 반영하는것 입니다.

플러시가 발생하는 3가지 경우
1. em.flush() 호출
2. 트랜잭션 커밋
3. JPQL 쿼리 실행

flush가 발생하면?

1차캐시의 스냅샷과 엔티티를 비교해 변경사항을 담은 update 쿼리를 쓰기지연 SQL 저장소에 추가하고 쓰기지연 SQL 저장소에 담긴 모든 변경사항을 DB에 전송합니다.

❗️ 정리
플러시는 영속성 컨텍스트를 비우는게 아닙니다.
영속성 컨텍스트의 변경사항을 DB에 동기화 시키는것 입니다.


준영속 상태

준영속 상태란 영속상태의 엔티티가 영속성 컨텍스트에서 분리되는 것을 의미합니다 (detached)
영속성 컨텍스트에서 분리되어 준영속상태가 되면 당연하게도 영속성 컨텍스트의 기능(변경 감지 등)들을 사용하지 못합니다.

준영속 상태로 만드는 방법 3가지
1. em.detached(entity) - 특정 엔티티만 준영속상태로 지정
2. em.clear() 영속성 컨텍스트를 완전히 초기화
3. em.close() 영속성 컨텍스트 종료
profile
작은 걸음이라도 꾸준히

0개의 댓글