[3] 영속성 관리

ttt-1-2·2026년 3월 22일

교재: 자바 ORM 표준 JPA 프로그래밍 


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

하나의 데이터베이스만 사용하는 애플리케이션에서는 보통 EntityManagerFactory를 하나만 생성해서 사용한다.

// 공장 생성, 초기 비용이 매우 큼
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");

Persistence.createEntityManagerFactory("jpabook")를 실행하면 META-INF/persistence.xml에 정의된 설정을 기반으로 EntityManagerFactory가 만들어진다. 이때 데이터베이스 연결 설정을 읽고 커넥션 풀을 준비하며 JPA 관련 초기화 작업이 수행되므로 생성 비용이 상당히 크다.

이후에는 필요할 때마다 EntityManagerFactory를 통해 엔티티 매니저를 생성해서 사용한다.

// 엔티티 매니저 생성, 비용이 거의 없음
EntityManager em = emf.createEntityManager();

엔티티 매니저는 실제로 데이터베이스와 CRUD 작업을 수행하는 객체이며 생성 비용이 낮기 때문에 요청 단위로 생성하고 사용 후 종료하는 방식이 일반적이다.

주의: 엔티티 매니저는 여러 스레드에서 동시에 공유해서 사용하면 안 된다.

2. 영속성 컨텍스트란?

영속성 컨텍스트 (persistence context) = 엔티티를 영구 저장하는 환경

em.persist(member);

3. 엔티티의 생명주기

엔티티는 4가지 상태가 있다:

(1) 비영속 (new/transient)

엔티티 객체를 생성만 한 상태로, 아직 영속성 컨텍스트와 전혀 관계가 없다. 데이터베이스와 연결되지 않은 순수 객체 상태다.

Member member = new Member(); // 비영속 상태
member.setName("Trang");

(2) 영속 (managed)

엔티티 매니저를 통해 영속성 컨텍스트에 저장된 상태다. 이 상태에서는 JPA가 엔티티를 관리하고 변경 사항을 자동으로 감지하여 DB에 반영한다.

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

(3) 준영속 (detached)

영속 상태였던 엔티티가 더 이상 영속성 컨텍스트에 의해 관리되지 않는 상태다. 변경을 해도 DB에 반영되지 않는다.

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

(4) 삭제 (removed): 삭제된 상태

엔티티가 삭제 대상으로 지정된 상태다. 이후 트랜잭션을 커밋하면 실제 DB에서도 삭제된다.

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

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

4.1 엔티티 조회

JPA에서 엔티티를 조회할 때는 1차 캐시(영속성 컨텍스트)를 먼저 사용한다.

1차 캐시는 엔티티 매니저 내부에 존재하는 저장 공간이며 같은 트랜잭션 내에서 엔티티를 효율적으로 관리하기 위해 사용된다. 1차 캐시의 키는 식별자 값(ID)이다. 이 식별자 값은 데이터베이스의 기본 키(PK)와 매핑된다.

조회 과정은 다음과 같이 동작한다.

  1. 먼저 1차 캐시에서 식별자 값으로 엔티티를 조회한다.
  2. 1차 캐시에 해당 엔티티가 없으면 데이터베이스에서 조회한다.
  3. 조회한 데이터를 기반으로 엔티티 객체를 생성한다.
  4. 생성된 엔티티를 1차 캐시에 저장한 후 반환한다.
// 엔티티를 생성한 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

// 엔티티를 영속
em.persist(member); // 1차 캐시에 저장됨

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

4.2 엔티티 등록

엔티티 등록은 새로운 객체를 생성하고 데이터베이스에 저장하는 과정이다. JPA에서는 persist() 메서드를 사용해서 엔티티를 영속 상태로 만든다.

Member member = new Member();
member.setName("Trang");

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

엔티티를 persist() 하면 바로 SQL이 실행되는 것이 아니라, 쓰기 지연 SQL 저장소에 INSERT 쿼리가 먼저 저장된다. 이후 트랜잭션을 커밋하는 시점에 SQL이 실제 데이터베이스로 전달된다.

tx.commit(); // 이때 INSERT SQL 실행

동작 흐름:

  1. 엔티티 생성 (비영속 상태)
  2. persist() 호출 → 영속 상태 전환
  3. INSERT SQL을 쓰기 지연 저장소에 저장
  4. commit 시점에 DB에 반영

4.3 엔티티 수정

엔티티 수정은 별도의 update 메서드를 호출하지 않고, 변경 감지(dirty checking)를 통해 자동으로 처리된다.

변경 감지 (Dirty Checking)

JPA는 엔티티의 변경을 자동으로 감지한다.

Member member = em.find(Member.class, 1L);
member.setName("New Name"); // 값 변경

별도의 update 호출 없이도 트랜잭션 commit 시점에 JPA가 변경을 감지해서 UPDATE SQL을 생성한다.

동작 방식:

  1. 엔티티를 조회하면 초기 상태를 스냅샷으로 저장한다.
  2. 트랜잭션 종료 시점에 현재 값과 비교한다.
  3. 변경된 경우에만 UPDATE SQL을 실행한다.

@DynamicUpdate

@org.hibernate.annotations.DynamicUpdate

이 옵션을 사용하면 변경된 컬럼만 포함한 UPDATE SQL이 생성된다. 불필요한 컬럼 업데이트를 줄이고 성능을 조금 더 최적화할 수 있다.

4.4 엔티티 삭제

엔티티를 삭제하려면 먼저 삭제 대상 엔티티를 조회해야 한다.

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

5. 플러시 (flush)

flush는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 과정이다.

  • 직접 호출: 개발자가 직접 flush를 호출하고 현재까지의 변경 사항이 즉시 DB에 반영된다.
em.flush();
  • 트랜잭션 커밋 시 자동 호출
tx.commit();

트랜잭션을 커밋하면 flush가 자동으로 실행된 후 commit이 진행된다. 따라서 대부분의 경우 flush를 직접 호출할 필요는 없다.

  • JPQL 쿼리 실행 시 자동 호출: JPQL을 실행하기 전에 flush가 자동으로 호출된다.

6. 준영속

준영속 상태는 엔티티가 더 이상 영속성 컨텍스트에 의해 관리되지 않는 상태다. 이 상태에서는 엔티티를 수정해도 변경 사항이 DB에 반영되지 않는다.

detach()

em.detach(member);

특정 엔티티를 준영속 상태로 전환한다. 해당 엔티티만 영속성 컨텍스트에서 분리된다.

clear()

em.clear();

영속성 컨텍스트를 초기화한다. 관리 중이던 모든 엔티티가 한 번에 준영속 상태가 된다.

close()

em.close();

영속성 컨텍스트를 종료한다. 이후에는 엔티티 매니저를 더 이상 사용할 수 없다.

병합

준영속 상태의 엔티티를 다시 영속 상태로 변형하려면 병합을 사용하면 된다.

// 정의
public <T> T merge(T entity);

// merge() 사용 예
Member mergeMember = em.merge(member);
// 전체 흐름 예시
// 1. 엔티티 조회 (영속 상태)
Member member = em.find(Member.class, 1L);

// 2. 준영속 상태로 변경
em.detach(member);

// 3. 값 변경 (DB 반영 안 됨)
member.setName("New Name");

// 4. merge 호출 → 다시 영속 상태
Member mergedMember = em.merge(member);

// 5. commit 시 UPDATE SQL 실행
tx.commit();

0개의 댓글