DB/JPA

[📗자바 ORM 표준 JPA 프로그래밍] 엔티티 매니저와 영속성 컨텍스트, 엔티티의 생명 주기

개발자 May 2024. 11. 10.

JPA의 엔티티 매니저(EntityManager)

JPA에서 엔티티 매니저(EntityManager)엔티티의 저장, 수정, 삭제, 조회 등 엔티티와 관련된 모든 작업을 관리하는 관리자 역할을 한다. 애플리케이션에서 JPA를 사용할 때, 엔티티 매니저가 제공하는 메서드를 통해 데이터베이스와의 상호작용을 쉽게 할 수 있다.

  • 엔티티 매니저는 엔티티 매니저 팩토리를 통해 생성할 수 있으며, 엔티티 매니저 팩토리는 여러 스레드에서 안전하게 공유할 수 있다.
  • 하지만 엔티티 매니저는 스레드 안전하지 않기 때문에 요청마다 새로운 인스턴스를 사용해야 한다. 하나의 엔티티 매니저를 여러 스레드에서 동시에 사용하면 동시성 문제가 발생할 수 있다.
// 엔티티 매니저 팩토리로부터 엔티티 매니저 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("example-unit");
EntityManager em = emf.createEntityManager();

영속성 컨텍스트(Persistence Context)

영속성 컨텍스트는 말 그대로 엔티티를 영구히 저장하는 환경을 의미하며, 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저가 영속성 컨텍스트에 엔티티를 보관하고 관리한다.

  • 예를 들어, em.persist(member);는 단순히 엔티티를 저장하는 것이 아니라, 영속성 컨텍스트에 저장하여 관리 상태로 만드는 것이다.
  • 영속성 컨텍스트는 엔티티 매니저가 생성될 때 만들어지며, 엔티티 매니저를 통해 영속성 컨텍스트에 접근할 수 있다.
// 회원 엔티티를 영속성 컨텍스트에 저장
Member member = new Member();
member.setId("memberA");
member.setUsername("회원A");

em.persist(member); // 영속성 컨텍스트에 엔티티 저장

이 코드에서 persist() 메서드는 단순히 데이터를 저장하는 것이 아니라, 회원 엔티티를 영속성 컨텍스트에 저장하여, 이후 관리되도록 한다.


엔티티의 생명 주기

엔티티는 다음과 같은 생명 주기를 가진다:

  1. 비영속 (new/transient): 엔티티가 생성되었지만 영속성 컨텍스트에 저장되지 않은 상태
  2. 영속 (managed): 영속성 컨텍스트에 저장되어 관리되는 상태
  3. 준영속 (detached): 영속성 컨텍스트에서 분리된 상태
  4. 삭제 (removed): 영속성 컨텍스트와 데이터베이스에서 삭제된 상태

생명 주기 단계별 상세 설명

1. 비영속 (new/transient)

new 키워드를 사용해 엔티티 객체를 새로 생성했으나, 아직 영속성 컨텍스트와 관련이 없는 상태이다.

Member member = new Member(); // 비영속 상태
member.setId("memberA");
member.setUsername("회원A");

이 단계에서는 데이터베이스에 저장되지 않으며, JPA에 의해 관리되지 않는다.

2. 영속 (managed)

persist() 메서드를 호출하여 엔티티를 영속성 컨텍스트에 저장하면 영속 상태가 된다. 영속 상태의 엔티티는 영속성 컨텍스트에 의해 관리되며, 1차 캐시에 저장된다.

em.persist(member); // 영속 상태

이 상태가 되면 엔티티 매니저가 해당 엔티티를 관리하며, 필요 시 변경 감지 및 지연 로딩 등 영속성 컨텍스트의 다양한 기능이 적용된다.

3. 준영속 (detached)

영속 상태의 엔티티가 영속성 컨텍스트와 분리된 상태이다. em.detach(entity) 메서드를 호출하여 엔티티를 영속성 컨텍스트에서 분리하거나, clear()로 영속성 컨텍스트를 초기화하면 준영속 상태가 된다.

em.detach(member); // member는 준영속 상태가 됨

준영속 상태의 엔티티는 영속성 컨텍스트의 관리에서 벗어나기 때문에, 데이터베이스와의 동기화가 이루어지지 않는다.

4. 삭제 (removed)

em.remove(entity)를 호출하여 엔티티를 삭제하면, 해당 엔티티는 삭제 상태가 된다. 엔티티가 영속성 컨텍스트와 데이터베이스에서 제거되는 상태로, 트랜잭션 커밋 시 삭제 쿼리가 실행된다.

em.remove(member); // 삭제 상태

삭제 상태의 엔티티는 영속성 컨텍스트와 데이터베이스에서 모두 제거된다.


영속성 컨텍스트의 주요 특징과 장점

1. 1차 캐시

영속성 컨텍스트는 내부적으로 1차 캐시를 가지고 있으며, 영속 상태의 엔티티를 1차 캐시에 저장하여 관리한다. 이 덕분에 find() 메서드로 조회할 때, 먼저 1차 캐시에서 찾고, 캐시에 없는 경우에만 데이터베이스를 조회한다.

// 처음 조회 시 DB에서 데이터를 가져와 1차 캐시에 저장
Member member1 = em.find(Member.class, "memberA");

// 같은 식별자로 조회하면 1차 캐시에서 가져옴
Member member2 = em.find(Member.class, "memberA");

System.out.println(member1 == member2); // true (동일한 인스턴스 반환)

2. 동일성 보장

1차 캐시에 동일한 식별자를 가진 엔티티는 동일한 인스턴스로 관리되므로, == 비교 시 항상 같은 인스턴스를 반환하여 동일성을 보장한다.

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

엔티티 매니저는 트랜잭션 커밋 시점에 한꺼번에 DB에 반영하는 쓰기 지연 기능을 제공한다. persist() 호출 시 DB에 즉시 저장하지 않고 쿼리 저장소에 INSERT 쿼리를 모아두었다가, 트랜잭션 커밋 시점에 DB로 한꺼번에 전송한다.

em.persist(member); // 쓰기 지연 SQL 저장소에 INSERT 쿼리 저장

// 트랜잭션 커밋 시 flush()가 호출되어 DB에 INSERT 쿼리 전송
em.getTransaction().commit();

4. 변경 감지 (Dirty Checking)

JPA는 영속성 컨텍스트에 저장된 엔티티의 변경 사항을 자동으로 감지하여 트랜잭션 커밋 시점에 DB에 반영한다. 엔티티의 데이터만 변경하면, JPA가 변경 내용을 감지하여 자동으로 업데이트 쿼리를 생성한다.

// 영속 상태에서 엔티티의 값을 변경
member.setUsername("변경된 이름");

// 트랜잭션 커밋 시 변경 감지(Dirty Checking)가 발생하여 업데이트 쿼리 전송
em.getTransaction().commit();

5. 지연 로딩

JPA는 필요할 때까지 연관된 엔티티를 로딩하지 않는 지연 로딩(Lazy Loading)을 지원하여 성능을 최적화한다. 실제로 연관된 엔티티가 사용될 때만 DB에서 데이터를 불러와 성능을 높일 수 있다.


준영속 상태의 특징과 사용법

특정 엔티티를 준영속 상태로 전환: detach()

em.detach(entity) 메서드는 특정 엔티티를 영속성 컨텍스트에서 분리하여 준영속 상태로 전환한다. 준영속 상태가 되면 엔티티는 더 이상 영속성 컨텍스트에서 관리되지 않는다.

em.persist(member); // 영속 상태
em.detach(member);  // 준영속 상태로 전환

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

em.clear() 메서드를 호출하면 영속성 컨텍스트가 관리하는 모든 엔티티가 준영속 상태가 된다.

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

em.clear(); // 모든 엔티티 준영속 상태로 전환

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

영속성 컨텍스트를 종료하면 모든 엔티티가 준영속 상태가 된다. 보통 트랜잭션 커밋 후에 자동으로 종료된다.

em.close(); // 영속성 컨텍스트 종료

준영속 상태의 엔티티를 다시 영속 상태로 만들기: merge()

준영속 상태의 엔티티를 다시 영속 상태로 변경하려면 em.merge(entity) 메서드를 사용한다. 이 메서드는 준영속 상태의 엔티티 정보를 복사하여 새로운 영속 상태의 엔티티를 반환한다.

Member detachedMember = new Member();
detachedMember.setId("memberA");
detachedMember.setUsername("회원A");

// 준영속 상태의 엔티티를 영속 상태로 병합
Member mergedMember = em.merge(detachedMember);

merge() 메서드를 사용하면 준영속 상태의 엔티티와 데이터베이스의 데이터가 다시 동기화된다.


Reference

자바 ORM 표준 JPA 프로그래밍(김영한 저)

댓글