본문 바로가기

Spring/자바 ORM 표준 JPA 프로그래밍

03. 영속성 관리 - 내부 동작 방식

💡 본 게시글은 김영한님의 인프런(Inflearn) 강의 자바 ORM 표준 JPA 프로그래밍 - 기본편에 대해 공부하고, 정리한 내용입니다.

1. 엔티티 매니저 팩토리 & 엔티티 매니저

1) 엔티티 매니저 팩토리

  1. - 생성 비용이 큼
  2. - 여러 스레드 동시 접근에 안전

2) 엔티티 매니저

  1. - 생성 비용이 거의 없음
  2. - 여러 스레드 동시 접근 시 동시성 문제 발생, 스레드 간 공유 금지

(1) 웹 어플리케이션 구조

  1. - EntityManagerFactory에서 다수의 엔티티 매니저를 생성
  2. - 엔티티 매니저는 데이터베이스 연결이 꼭 필요한 시점까지 커넥션을 얻지 않음
  3. - 보통 트랜잭션을 시작할 때 커넥션을 획득
  4. - JPA 구현체들은 EntityManagerFactory 생성 시 커넥션 풀도 함께 생성
  5. - J2EE 환경(스프링 포함)에서 JPA를 사용할 경우, 해당 컨테이너가 제공하는 데이터 소스를 사용

2. 엔티티 생명주기

1) 엔티티 상태

  1. - 비영속 (new/transient): 영속성 컨텍스트와 전혀 관계가 없는 상태
  2. - 영속 (managed): 영속성 컨텍스트에 저장된 상태
  3. - 준영속 (detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
  4. - 삭제 (removed): 영속성 컨텍스트와 데이터베이스에서 삭제된 상태

2) 상태별 코드 예시

(1) 비영속

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

(2) 영속

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

(3) 준영속

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

(4) 삭제

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

3. 영속성 컨텍스트

1) 영속성 컨텍스트 개념

  1. - 엔티티를 영구 저장하는 환경을 의미
  2. - 엔티티 매니저로 엔티티를 저장하거나 조회 시 영속성 컨텍스트에 보관 및 관리
  3. - 여러 엔티티 매니저가 같은 영속성 컨텍스트에 접근 가능

2) 영속성 컨텍스트의 특징

  1. - 식별자 값 관리: 엔티티는 식별자 값(@Id)으로 구분되며, 영속 상태에서는 식별자 값이 반드시 필요
  2. - 데이터베이스 저장 시점: 보통 트랜잭션 커밋 시 영속성 컨텍스트에 저장된 엔티티가 데이터베이스에 반영 (플러시)
  3. - 1차 캐시: 영속성 컨텍스트는 내부에 1차 캐시를 가지고 있으며, 영속 상태의 엔티티가 저장됨
  4. - 동일성 보장: 1차 캐시에서 동일한 엔티티 인스턴스의 참조를 반환하여 동일성 보장
  5. - 쓰기 지연: 트랜잭션 커밋 시까지 INSERT SQL을 모아 두었다가 한 번에 실행
  6. - 변경 감지: 엔티티의 변경사항을 자동으로 감지하여 데이터베이스에 반영
  7. - 지연 로딩: 필요한 시점까지 데이터베이스에 접근하지 않고, 실제 사용할 때 로딩

4. 엔티티 조회 매커니즘

1) 1차 캐시에서 조회

- em.find() 메소드를 호출하면, 먼저 1차 캐시에서 조회한 후, 존재하지 않으면 데이터베이스에서 조회

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

// 1차 캐시에 저장됨
em.persist(member);

// 1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
            

2) 데이터베이스에서 조회

- 1차 캐시에 엔티티가 존재하지 않을 경우, 데이터베이스를 조회하여 엔티티를 생성하고, 1차 캐시에 저장한 후 반환

Member findMember2 = em.find(Member.class, "member2");
            

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

- 1차 캐시에 있는 같은 엔티티 인스턴스의 ‘참조’를 반환하기 때문에 동일성이 보장됩니다

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

System.out.println(a == b); // 동일성 비교, 결과는 참
            

5. 엔티티 저장 매커니즘

1) 쓰기 지연

- 엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않으며, 커밋 시점에 INSERT SQL을 모아서 전달

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// 엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작

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

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

6. 엔티티 변경 매커니즘

1) 변경 감지 (Dirty Checking)

- 엔티티의 변경 사항을 자동으로 감지하여 데이터베이스에 반영하는 기능

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

// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");

// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);

// em.update(member) 이런 코드가 있어야 하지 않을까?
transaction.commit(); //[트랜잭션] 커밋
            

- JPA는 엔티티를 영속성 컨텍스트에 보관할 때 최초 상태를 복사해서 스냅샷을 저장해 둡니다. 플러시 시점에 스냅샷과 엔티티를 비교하여 변경된 엔티티를 찾습니다.


7. 엔티티 삭제 매커니즘

1) 삭제 처리

- 엔티티를 삭제할 때, 삭제 쿼리를 쓰기 지연 SQL 저장소에 등록하고, 트랜잭션을 커밋하여 플러시를 호출하면 실제 데이터베이스에 삭제 쿼리를 전달

Member memberA = em.find(Member.class, "memberA"); // 삭제 대상 엔티티 조회
em.remove(memberA); // 엔티티 삭제
            

- em.remove(memberA);를 호출하는 순간, memberA는 영속성 컨텍스트에서 제거됩니다.


8. 플러시 (flush)

1) 플러시의 의미

- 플러시(flush)는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 작업을 의미합니다

  1. - 변경 감지가 동작하여 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교하여 수정된 엔티티를 찾고 수정 쿼리를 쓰기 지연 SQL 저장소에 저장
  2. - 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)

2) 플러시 발생 시점

  1. - flush() 직접 호출: 엔티티 매니저의 flush() 메소드를 직접 호출.
  2. - 트랜잭션 커밋 시 자동 호출.
  3. - JPQL 쿼리 실행 시 자동 호출: JPQL, Criteria 호출 시에도 플러시가 실행됨.

3) 플러시 모드 옵션

  1. - FlushModeType.AUTO: 커밋이나 쿼리를 실행할 때 플러시 (기본값).
  2. - FlushModeType.COMMIT: 커밋할 때만 플러시.
em.setFlushMode(FlushModeType.COMMIT); // EntityManager에 플러시 모드 직접 설정
            

- 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 것이지, 영속성 컨텍스트의 엔티티를 지우는 작업이 아님을 기억하세요.


9. 준영속 상태

1) 준영속 상태란?

- 준영속 상태란 엔티티가 영속성 컨텍스트에서 분리된 상태를 의미합니다. 준영속 상태의 엔티티는 영속성 컨텍스트가 제공하는 기능을 사용할 수 없습니다.

2) 준영속 상태로 변경하는 방법

  1. - em.detach(entity): 특정 엔티티만 준영속 상태로 전환
  2. - em.clear(): 영속성 컨텍스트를 완전히 초기화
  3. - em.close(): 영속성 컨텍스트를 종료

3) 준영속 상태로 전환: detach()

public void testDetached(){    
  // 회원 엔티티 생성, 비영속 상태
  Member member = new Member();
  member.setId("memberA");
  member.setUsername("회원A");
  
  // 회원 엔티티 영속 상태
  em.persist(member);
  
  // 회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
  em.detach(member);
  
  // 트랜잭션 커밋
  transaction.commit(); 
}
            

- 플러시가 일어나기 전 준영속 상태로 변경했기 때문에 위 코드는 어떤 일도 일어나지 않습니다.

4) 영속성 컨텍스트 초기화: clear()

- 해당 영속성 컨텍스트의 모든 엔티티를 준영속 상태로 만듭니다.

5) 영속성 컨텍스트 종료: close()

- 영속성 컨텍스트가 관리하던 모든 엔티티가 준영속 상태가 됩니다.

6) 병합: merge()

- 준영속 상태의 엔티티를 다시 영속 상태로 변경합니다. 새로운 영속 상태의 엔티티를 반환하며, 파라미터로 넘어온 준영속 엔티티는 병합 후에도 준영속 상태입니다. 비영속 엔티티도 영속 상태로 만들 수 있습니다.

Member member = new Member();
Member newMember = em.merge(member); // 비영속 병합
tx.commit(); // 가능
            

- 병합은 준영속/비영속을 신경 쓰지 않고, 식별자 값으로 엔티티를 조회할 수 있다면 불러서 병합하고, 그렇지 않다면 새로 생성해서 병합합니다. 따라서 병합은 save or update 기능을 수행합니다.

7) 준영속 상태의 특징

  1. - 거의 비영속 상태에 가깝습니다.
  2. - 영속성 컨텍스트가 관리하지 않으므로 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 어떤 기능도 동작하지 않음
  3. - 식별자 값을 가지고 있습니다. 이미 한 번 영속 상태였으므로 반드시 식별자 값을 가지고 있습니다.
  4. - 지연 로딩을 할 수 없습니다.

'Spring > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글

06. 다양한 연관관계 매핑  (1) 2024.09.08
05. 연관관계 매핑 기초  (0) 2024.09.08
04. 엔티티 매핑  (1) 2024.09.08
02. JPA 시작하기  (0) 2024.08.11
01. JPA 소개  (0) 2024.07.24