백엔드/JPA

[JPA] JPA 영속성 컨텍스트란?

dami97 2024. 1. 16. 21:00
반응형

JPA 에서 가장 중요하다고 생각하는 영속성 컨텍스트에 대해 정리한 글입니다.

 

영속성 컨텍스트란?

엔티티 매니저로 엔티티를 저장하거나 조회하면 영속성 컨텍스트에 엔티티를 보관하고 관리한다.
한 마디로 요약하면 "엔티티를 영구 저장하는 환경" 이다.

엔티티의 4가지 상태

먼저 엔티티에는 4가지 상태가 존재한다.

  • 비영속
  • 영속
  • 준영속
  • 삭제

1. 비영속

Member member = new Member(); // 객체 생성
member.setId("member1");
member.setUsername("회원1");

순수한 객체 상태이며 영속성 컨텍스트나 데이터베이스와는 전혀 관련이 없는 상태

2. 영속

em.persist(member); // 객체를 저장

영속성 컨텍스트에 저장한 상태, 영속 상태로써 영속성 컨텍스트에 의해 관리된다

병합 merge () - 준영속 비영속을 신경 쓰지 않고 식별자 값으로 엔티티를 조회할  수 있으면 부르고 없다면 새로생성해서 병합한다.

Member mergeMember = em.merge(member);

새로운 영속 상태의 엔티티를 반환한다.
매개변수로 넣은 엔티티와 다른 인스턴스를 반환하는 것에 주의!

3. 준영속

// 아래의 메서드들은 준영속 상태로 만든다.
em.detach(member); // 영속성 컨텍스트에서 분리
em.close(); // 영속성 컨텍스트를 닫기
em.clear(); // 영속성 컨텍스트를 초기화

영속성 컨텍스트가 관리하던 영속 상태의 엔티티가 영속성 컨텍스트가 관리하지 않는 상태가 된것
detach(member); 는 특정 엔티티 하나를 준영속 상태로 만들지만
clear(); 는 해당 영속성 컨텍스트의 모든 엔티티를 준영속 상태로 만듬 close(); 도 마찬가지

4. 삭제

em.remove(member); // 삭제

영속성 컨텍스트와 데이터베이스에서 삭제한다.


영속성 컨텍스트의 장점 ( CRUD )

 

1. 조회 (READ)

1차캐시

  • 영속 상태의 엔티티는 모두 이곳에 저장됨
  • 키는 @Id로 매핑한 식별자값(데이터베이스 기본 키), 값은 엔티티 인스턴스
  • em.find() 를 호출할때 1차 캐시에 찾는 엔티티가 있으면 데이터베이스를 조회하지 않고 메모리에 있는 1차 캐시에서 조회 -> 성능상 이점

동일성 보장

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
// a == b 는 true
  • 동일성 비교 시 1차 캐시에서 같은 엔티티 인스턴스가 반환되기 때문에 결과는 true가 반환됨 즉 엔티티의 동일성을 보장한다.

2. 등록  (CREATE)

 쓰기 지연

  • 트랜잭션 커밋 직전까지 내부 쿼리 저장소에 INSERT SQL을 모아두고 트랜잭션을 커밋하면 영속성 컨텍스트를 플러시 한다.
    *플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화 하는 작업
  • 모아둔 등록 쿼리를 데이터베이스에 한 번에 전달해서 성능 최적화가 가능함

3. 수정 (UPDATE)

변경 감지 (dirty checking)

  • 따로 업데이트 UPDATE SQL 을 보내지 않아도 엔티티의 변경사항을 데이터베이스에 자동으로 반영하는 기능
  • 변경 감지는 영속 상태의 엔티티에만 적용됨
  • 변경 감지로 인해 실행된 UPDATE SQL은 모든 필드를 업데이트 한다.
    • 즉 엔티티의 name 만 변경하더라도 SET NAME = ? , AGE = ? ... 로 쿼리가 생성됨
    • @DynamicInsert, @DynamicUpdate 등 어노테이션으로 전략 선택 가능

변경 감지 순서

  • 1. JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해둠(스냅샷)
  • 2. 트랜잭션 커밋 시점 플러시가 먼저 호출됨 스냅샷과 엔티티를 비교(변경된 엔티티 탐색)
  • 3. 변경된 엔티티가 있으면 수정 쿼리 생성 -> 쓰기 지연
  • 4. 모아둔 SQL 을 데이터베이스에 보낸다.
  • 5. 트랜잭션 커밋

4. 삭제 (DELETE)

  • 엔티티를 삭제하려면 먼저 삭제 대상 엔티티를 조회해야 한다.
Member memberA = em.find(Member.class, "memberA"); // 삭제 대상 엔티티 조회
em.remove(memberA); // 엔티티 삭제
  • em.remove() 에 삭제 대상 엔티티를 넘겨주면 등록과 비슷하게 쓰기 지연 SQL에 저장
  • em.remove(memberA) 호출 시 영속성 컨텍스트에서 제거되기 때문에 재사용하지 말것

 

그렇다면 트래잭션 커밋 시점 먼저 호출되는 플러시 란 뭘까?

트랜잭션이 커밋하는 순간 데이터베이스에 반영함(플러시)

  • 플러시(flush()) 는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.
  • 플러시가 되는 경우 3가지
    • em.flush() 직접 호출 (거의 사용하지 않음)
    • 트랜잭션 커밋 시 (자동으로 호출)
      • SQL을 전달하지 않고 커밋하면 반영되지 않는다.
      • 데이터베이스에 변경 내용을 SQL로 전달해서 반영해야 하기 때문
    • JPQL 쿼리 실행 시 (자동으로 호출)
      • 영속성 컨텍스트에는 있지만 데이터베이스에는 반영되지 않은 엔티티를 조회하는 문제를 방지
      • 식별자를 기준으로 조회하는 find() 메소드를 호출할 때는 플러시가 실행되지 않음
  • 플러시 모드를 직접 지정할 수도 있는데 보통 기본설정인 AUTO 를 그대로 사용한다.

플러시 정리

플러시는 영속성 컨텍스트에 보관된 엔티티를 지우는 것이 아니라 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화 하는것!

 

 

 

 

 

 

참고자료 - 자바 ORM 표준 JPA 프로그래밍 (김영한)

반응형