기록

[자바 ORM 표준 JPA 프로그래밍] 영속성 컨텍스트와 엔티티 생명주기 (3장) 본문

교육/책

[자바 ORM 표준 JPA 프로그래밍] 영속성 컨텍스트와 엔티티 생명주기 (3장)

youngyin 2024. 11. 16. 23:48

시작하면서

JPA(Java Persistence API)를 사용하면 데이터베이스와 객체 간의 매핑을 쉽고 효율적으로 처리할 수 있습니다. 그 중에서도 영속성 관리는 JPA의 핵심 기능 중 하나로, 애플리케이션의 데이터 일관성과 효율적인 데이터베이스 접근을 가능하게 합니다.

이 글은 '자바 ORM 표준 JPA 프로그래밍' 책을 기반으로 정리한 내용입니다. 이번 포스팅에서는 "3장. 영속성관리"의 내용을 정리해보았습니다. 아래 그림과 자료들은 책의 내용과 이해한 내용을 바탕으로 재구성한 내용입니다.

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

1.1 엔티티 매니저 팩토리(EntityManagerFactory)

  • EntityManagerFactoryEntityManager 객체를 생성하는 "공장"입니다. 애플리케이션 전체에서 단 하나만 생성해야 하며, 이는 엔티티 매니저 팩토리 생성에 큰 비용이 들기 때문입니다. 따라서 애플리케이션 시작 시점에 하나의 인스턴스를 생성해 재사용하는 것이 좋습니다.

1.2 엔티티 매니저 (EntityManager)

  • EntityManagerFactory를 통해 필요할 때마다 EntityManager를 생성합니다. EntityManager는 데이터베이스 작업(등록, 조회, 수정, 삭제)을 담당하며, 트랜잭션 단위로 사용한 후 닫아야 합니다. 이를 통해 데이터베이스와의 자원을 효율적으로 관리할 수 있습니다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("example-unit"); 
EntityManager em = emf.createEntityManager(); 

엔티티 매니저는 스레드 간에 공유하지 말고, 사용 후 반드시 close() 해야 합니다.

2. 영속성 컨텍스트란?

영속성 컨텍스트는 엔티티를 영구적으로 저장하는 공간으로, 엔티티 객체가 데이터베이스와의 관계를 맺기 전에 일종의 버퍼 역할을 합니다. JPA는 이 공간을 통해 엔티티를 관리하고 상태를 추적합니다.

Member member = new Member(); 
member.setId("1"); 
member.setName("John Doe"); 
em.persist(member); // 엔티티가 영속성 컨텍스트에 저장됨 

 

위 코드에서 em.persist(member); 호출 시 Member 객체는 영속성 컨텍스트에 저장되어 관리됩니다. 이로 인해 JPA는 해당 엔티티의 변경 사항을 추적하고, 트랜잭션이 커밋될 때 데이터베이스에 반영합니다.

영속성 컨텍스트는 엔티티를 관리하면서 트랜잭션 단위로 데이터를 동기화하는 역할을 하고, 1차 캐시와 같은 기능을 제공합니다. 프록시는 지연 로딩을 지원하는 데 사용되며, 캐시는 데이터베이스 접근 횟수를 줄이는 데 초점이 있습니다.

4. 영속성 컨텍스트의 주요 특징

4.1 1차 캐시와 데이터 조회

 

영속성 컨텍스트는 1차 캐시 역할을 합니다. @Id로 매핑된 식별자를 통해 엔티티를 저장하고, 엔티티 조회 시 먼저 1차 캐시에서 검색합니다. 이는 데이터베이스 접근 횟수를 줄이고 성능을 향상시킵니다.

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

위 코드에서 member2는 데이터베이스 접근 없이 1차 캐시에서 조회됩니다. 따라서 동일한 키로 조회한 엔티티는 항상 같은 객체를 반환합니다.

동일성과 동등성: 동일한 키로 조회한 엔티티가 같은 객체를 반환하는 이유는 1차 캐시 덕분입니다. 이는 메모리 내에서 동일한 인스턴스를 관리하므로 == 비교에서도 true가 반환됩니다.

4.2 엔티티 등록과 쓰기 지연

 

엔티티를 등록할 때 JPA는 내부적으로 쓰기 지연(write-behind) 메커니즘을 사용하여 데이터베이스에 일괄적으로 저장합니다.

em.persist(member1); 
em.persist(member2); 

// 트랜잭션 커밋 시점에 INSERT 쿼리 실행 
em.getTransaction().commit();

쓰기 지연을 통해 여러 작업을 모아 한 번에 처리하므로, 데이터베이스 성능을 높이는 데 큰 도움이 됩니다.

4.3 엔티티 수정과 변경 감지 (Dirty Checking)

JPA는 변경 감지(Dirty Checking) 기능을 통해 엔티티의 상태가 변경되었는지 자동으로 추적합니다. 개발자가 직접 update 메소드를 호출하지 않아도, 객체를 자동으로 업데이트합니다.

JPA는 기본적으로 모든 필드를 업데이트하는 방식으로 동작합니다. 이는 데이터 일관성을 보장하기 위한 조치이며, 변경된 필드만 업데이트하도록 최적화하는 것이 복잡하고 위험할 수 있기 때문입니다. 그러나, 특정 필드만 동적으로 업데이트하고 싶을 때는 @DynamicUpdate 어노테이션을 사용하여 선택적으로 업데이트할 수 있습니다.

member.setName("Jane Doe"); // 값만 변경 

// 트랜잭션 커밋 시점에 UPDATE 쿼리 실행 

 

변경 감지를 통해 최소한의 쿼리로 데이터베이스를 업데이트할 수 있습니다.

5. 플러시(Flush)

5.1 플러시 개념과 호출 시점

플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 작업입니다. 플러시는 다음과 같은 경우에 호출됩니다.

  • 트랜잭션 커밋 시 자동 호출
  • JPQL 실행 시 자동 호출 (JPQL 실행 전 데이터 동기화를 위해)
    • JPQL 실행 직전에 플러시가 호출되는 이유는 영속성 컨텍스트의 변경 내용을 데이터베이스와 동기화하여, 1차 캐시와 데이터베이스 간의 데이터 일관성을 맞추기 위함입니다. 즉, 플러시는 영속성 컨텍스트에 있는 엔티티의 변경 사항을 실제 데이터베이스에 반영하여 JPQL 실행 시 최신 데이터를 조회할 수 있도록 합니다. 이는 1차 캐시와 데이터베이스를 연동하기 위해 필요한 단계입니다.
  • 명시적으로 em.flush() 호출
em.flush(); // 변경 사항을 데이터베이스에 반영 

플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영할 뿐, 영속성 컨텍스트 자체를 비우지는 않습니다.

3. 엔티티의 생명주기

 

JPA 엔티티는 다음과 같은 생명주기를 거칩니다.

  1. 비영속 (Transient): 단순히 메모리에만 존재하며 영속성 컨텍스트와 전혀 관련 없는 상태입니다.
  2. Member member = new Member(); // 비영속 상태
  3. 영속 (Persistent): 영속성 컨텍스트에 의해 관리되는 상태로, em.persist()를 호출하여 비영속 상태에서 영속 상태로 변환됩니다.
  4. em.persist(member); // 영속 상태
  5. 준영속 (Detached): 영속성 컨텍스트가 더 이상 엔티티를 관리하지 않는 상태입니다. 예를 들어, em.detach(member)를 호출하면 엔티티는 준영속 상태로 변경됩니다.
  6. em.detach(member); // 준영속 상태
  7. 삭제 (Removed): 엔티티가 삭제된 상태로, em.remove() 호출 시 영속성 컨텍스트와 데이터베이스에서 삭제됩니다.
  8. em.remove(member); // 삭제 상태

준영속 상태는 특정 엔티티만 영속성 컨텍스트에서 분리하여 변경사항이 데이터베이스에 반영되지 않도록 할 때 사용됩니다. 예를 들어, UI 상에서 특정 데이터를 편집하다가 취소할 때 변경사항이 반영되지 않도록 하려는 경우 유용합니다.

7. 마무리

이번 글에서는 JPA의 영속성 컨텍스트, 엔티티의 생명주기, 플러시와 준영속 상태에 대해 알아보았습니다. JPA의 영속성 관리는 데이터베이스와 객체 간의 매핑을 효율적으로 처리하기 위한 중요한 개념입니다. 이 개념을 잘 이해하고 활용하면 복잡한 데이터베이스 연산도 처리할 수 있습니다.

Comments