본문 바로가기
프로그래밍/JPA

03장. 영속성 관리

by 직장인 투자자 2022. 1. 14.

개요

  • JPA가 제공하는 기능은 크게 엔티티와 테이블을 매핑하는 설계부분과 매핑한 엔티티를 실제 사용하는 부분으로 나눌 수 있다.
  • 3장 영속성 관리에서는 매핑한 엔티티를 엔티티 매니저를 통해 어떻게 사용되는지 보여준다.
  • 개발자 입장에서 엔티티 매니저는 엔티티를 저장하는 가상의 데이터베이스로 생각하면 된다.

 

 

 

엔티티 매니저 팩토리와 엔티티 매니저

  • 데이터 베이스를 하나만 사용하는 애플리케이션은 일반적으로 EntityMangerFactory를 하나만 생성한다.
  • 엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전하므로 서로 다른 스레드 간에 공유해도 되지만, 엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간에 절대 공유하면 안 된다.

영속성 콘텍스트란?

  • JPA를 이해하는데 가장 중요한 용어는 영속성 콘텍스트(persistence context)다.
  • 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 콘텍스트에 엔티티를 보관하고 관리한다.
em.persist(member);
  • 정확히 얘기하면 위 코드는 엔티티 매니저를 사용해서 멤버 엔티티를 영속성 콘텍스트에 저장한다.
  • 영속성 콘텍스트는 엔티티 매니저를 생성할 때 하나 만들어진다.
  • 그리고 엔티티 매니저를 통해서 영속성 콘텍스트에 접근할 수 있고, 영속성 콘텍스트를 관리할 수 있다.

엔티티의 생명주기

  • 엔티티에는 4가지 상태가 존재한다.
  • 비영속(new/transient): 영속성 콘텍스트와 전혀 관계가 없는 상태
  • 영속(managed): 영속성 콘텍스트에 저장된 상태
  • 준영속(detached): 영속성 콘텍스트에 저장되었다가 분리된 상태
  • 삭제(removed): 삭제된 상태

생명주기
생명주기

비영속

엔티티 객체를 생성했다. 지금은 순수한 객체 상태이다. 따라서 영속성 콘텍스트나 데이터베이스와 전혀 관련이 없다.

Member member  =  new Member();
member.setId("member1");
member.setUserName("회원1");

영속

엔티티 매니저를 통해서 영속성 콘텍스트에 저장했다. 이렇게 영속성 콘텍스트가 관리하는 엔티티를 영속 상태라 한다.

// 객체를 저장한 상태(영속)
em.persist(member);

준영속

영속성 콘텍스트가 관리하던 영속 상태의 엔티티를 영속성 콘텍스트가 관리하지 않으면 준영속 상태가 된다.

em.detach를 호출하면 된다.

// 회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);

삭제

엔티티를 영속성 콘텍스트와 데이터베이스에서 삭제한다.

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

영속성 콘텍스트의 특징

  • 영속성 콘텍스트와 식별자 값영속성 콘텍스트는 엔티티를 식별자 값(@Id로 테이블의 기본키와 매핑한 값)으로 구분한다. 따라서 영속 상태는 식별자 값이 반드시 있어야 한다.
  • 영속성 콘텍스트와 데이터베이스 저장JPA는 보통 트랜잭션을 커밋하는 순간 영속성 콘텍스트에 새로 저장된 엔티티를 데이터베이스에 반영하는데 이것을 플러시 라 한다.
  • 영속성 콘텍스트의 장점
  1. 1차 캐시
  2. 동일성 보장
  3. 트랜잭션을 지원하는 쓰기 지연
  4. 변경 감지
  5. 지연 로딩

1차 캐시

  • 영속성 콘텍스트는 내부에 캐시를 가지고 있는데 이것을 1차 캐시라 한다. 영속 상태의 엔티티는 모두 이곳에 저장된다.
    이 캐시의 구조는 @Id를 key로 갖고 엔티티 인스턴스를 value로 갖는 map이라고 보면 된다.
// 엔티티를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

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

영속성 콘텍스트에서 데이터를 저장하고 조회하는 기준은 모두 데이터베이스의 기본키 값이다.

Member member = em.find(Member.class, "member1");
  • 1차 캐시에서 조회em.find()를 호출하면 먼저 1차 캐시에서 엔티티를 찾고 만약 찾는 엔티티가 1차 캐시에 없으면 데이터베이스에서 조회한다.

1차 캐시에서 조회
1차 캐시에서 조회

  • 데이터베이스에서 조회만약 em.find()를 호출했는데 엔티티가 1차 캐시에 없으면 엔티티 매니저는 데이터베이스를 조회해서 엔티티를 생성한다.

db 에서 조회
db 에서 조회

 

  1. em.find(Member.class, "member2)를 실행한다.
  2. member2가 1차 캐시에 없으므로 데이터베이스에서 조회한다.
  3. 조회한 데이터로 member2 엔티티를 생성해서 1차 캐시에 저장한다.(영속 상태)
  4. 조회한 엔티티를 반환한다.이제 member1, member2 엔티티 인스턴스는 1차 캐시에 있다. 이 엔티티들 조회하면 메모리에 있는 1차 캐시에서 바로 불러온다. 따라서 성능상 이점을 누릴 수 있다.

동일성 보장

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

System.out.println(a == b); // 동일성 비교

em.find(Member.class, "member1")를 반복해서 호출해도 영속성 콘텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스를 반환한다.
따라서 둘은 같은 인스턴고 그 결과는 참이다.

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

엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 INSERT SQL을 차곡차곡 모아둔다.

그리고 트랜잭션을 커밋할 때 모아둔 쿼리를 데이터베이스 보내는데 이것을 트랜잭션을 지원하는 쓰기 지연이라고 한다.

em.persist(memberA);
em.persist(memberB);
// 여기까지 INSERT SQL을 데이터베이스 보내지 않는다.

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

쓰기지연
쓰기지연

  • 마지막에 트랜잭션을 커밋을 하게 된다.
  • 커밋을 하면 엔티티 매니저는 우선 영속성 콘텍스트를 플러시 한다.
  • 플러시는 영속성 콘텍스트의 변경 내용을 데이터베이스에 동기화하는 작업인데 이때 등록, 수정, 삭제한 엔티티를 데이터베이스에 반영한다.
  • 다시 말하면 플러시는 쓰기 지연 SQL 저장소에 모인 쿼리를 데이터베이스에 보내는 것이다.
  • 영속성 콘텍스트의 변경 내용을 데이터베이스에 동기화한 후에 실제 데이터베이스 트랜잭션을 커밋한다.
  • 쓰기 지연을 잘 활용하면 등록 쿼리를 모아뒀다가 한 번에 데이터베이스에 전달해서 성능을 최적화할 수 있다.

 

 

 

변경 감지

  • JPA에는 엔티티 수정에 필요한 update() 문이 존재하지 않는다. 엔티티의 데이터만 변경하면 데이터베이스에 자동으로 반영된다. 이것을 변경 감지라고 한다.

변경감지
변경감지

  • JPA는 엔티티를 영속성 콘텍스트에 보관할 때, 최초 상태를 복사해서 저장해두는데 이것을 스냅숏이라 한다.
  • 트랜잭션을 커밋하면 엔티티 매니저 내부에서 먼저 플러시가 호출된다.
  • 엔티티와 스냅숏을 비교해서 변경된 엔티티를 찾는다.
  • 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보낸다.
  • 쓰기 지연 저장소의 SQL을 데이터베이스에 보낸다.
  • 데이터베이스 트랜잭션을 커밋한다.변경 감지는 영속성 콘텍스트가 관리하는 영속 상태의 엔티티에만 적용된다.
    변경 감지를 통해 만들어지는 JPA의 업데이트 구문은 엔티티의 모든 필드를 업데이트한다.

플러시

  • 플러시는 앞에서 얘기했듯이 영속성 콘텍스트의 변경 내용을 데이터베이스 반영한다.플러시를 실행하면 다음과 같은 일이 일어난다.
  1. 변경 감지가 동작해서 영속성 콘텍스트에 있는 모든 엔티티를 스냅과 비교해서 수정된 엔티티를 찾는다.
  2. 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록한다.
  3. 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송한다.(등록, 수정, 삭제 쿼리)영속성 콘텍스트를 플러시 하는 3가지 방법
  4. em.flush()를 직접 호출한다.
  5. 트랜잭션 커밋 시 플러시가 자동 호출된다.
  6. JPQL 쿼리 실행 시 플러시가 자동 호출된다.플러시 모드 옵션
  • FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 플러시(기본값)
  • FlushModeType.COMMIT : 커밋할 때만 플러시

준영속

  • 준영속 상태의 엔티티는 영속성 콘텍스트가 관리하지 않으므로 영속성 콘텍스트가 제공하는 어떠한 기능도 동작하지 않는다.
  • 준영속 상태로 만드는 방법 3가지
  1. em.detach(entity): 특정 엔티티만 준영속 상태로 전환한다.
  2. em.clear(): 영속성 콘텍스트를 완전히 초기화한다.
  3. em.close(): 영속성 콘텍스트를 종료한다.

detach 실행전
detach 실행전

정리

  1. 엔티티 매니저는 엔티티 매니저 팩토리에서 생성된다.
  2. 엔티티 매너지가 만들어지면 그 내부에 영속성 콘텍스트도 함께 만들어진다.
  3. 영속성 콘텍스트를 가상의 데이터베이스로 생각해도 무방하며, 영속성 콘텍스트 덕분에, 1차 캐시, 동일성 보장, 트랜잭션을 지원하는 쓰기 지연, 변경 감지, 지연 로딩 기능을 사용할 수 있다.
  4. 영속성 콘텍스트에 저장된 엔티티는 플러시 시점에 데이터베이스에 반영되는데 일반적으로 커밋 시점으로 보면 된다.
반응형

'프로그래밍 > JPA' 카테고리의 다른 글

05장. 연관관계 매핑기초  (4) 2022.01.22
04장. Entity Mapping  (6) 2022.01.18
02. JPA 시작  (10) 2022.01.11
01. JPA 소개  (4) 2022.01.08

댓글