교재: 자바 ORM 표준 JPA 프로그래밍
하나의 데이터베이스만 사용하는 애플리케이션에서는 보통 EntityManagerFactory를 하나만 생성해서 사용한다.
// 공장 생성, 초기 비용이 매우 큼
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
Persistence.createEntityManagerFactory("jpabook")를 실행하면 META-INF/persistence.xml에 정의된 설정을 기반으로 EntityManagerFactory가 만들어진다. 이때 데이터베이스 연결 설정을 읽고 커넥션 풀을 준비하며 JPA 관련 초기화 작업이 수행되므로 생성 비용이 상당히 크다.
이후에는 필요할 때마다 EntityManagerFactory를 통해 엔티티 매니저를 생성해서 사용한다.
// 엔티티 매니저 생성, 비용이 거의 없음
EntityManager em = emf.createEntityManager();
엔티티 매니저는 실제로 데이터베이스와 CRUD 작업을 수행하는 객체이며 생성 비용이 낮기 때문에 요청 단위로 생성하고 사용 후 종료하는 방식이 일반적이다.
주의: 엔티티 매니저는 여러 스레드에서 동시에 공유해서 사용하면 안 된다.
영속성 컨텍스트 (persistence context) = 엔티티를 영구 저장하는 환경
em.persist(member);
엔티티는 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); // 삭제 상태
JPA에서 엔티티를 조회할 때는 1차 캐시(영속성 컨텍스트)를 먼저 사용한다.
1차 캐시는 엔티티 매니저 내부에 존재하는 저장 공간이며 같은 트랜잭션 내에서 엔티티를 효율적으로 관리하기 위해 사용된다. 1차 캐시의 키는 식별자 값(ID)이다. 이 식별자 값은 데이터베이스의 기본 키(PK)와 매핑된다.
조회 과정은 다음과 같이 동작한다.
// 엔티티를 생성한 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
// 엔티티를 영속
em.persist(member); // 1차 캐시에 저장됨
// 1차 캐시 에서 조회
Member findMember = em.find(Member.class, "member1");
엔티티 등록은 새로운 객체를 생성하고 데이터베이스에 저장하는 과정이다. JPA에서는 persist() 메서드를 사용해서 엔티티를 영속 상태로 만든다.
Member member = new Member();
member.setName("Trang");
em.persist(member); // 영속 상태
엔티티를 persist() 하면 바로 SQL이 실행되는 것이 아니라, 쓰기 지연 SQL 저장소에 INSERT 쿼리가 먼저 저장된다. 이후 트랜잭션을 커밋하는 시점에 SQL이 실제 데이터베이스로 전달된다.
tx.commit(); // 이때 INSERT SQL 실행
동작 흐름:
persist() 호출 → 영속 상태 전환엔티티 수정은 별도의 update 메서드를 호출하지 않고, 변경 감지(dirty checking)를 통해 자동으로 처리된다.
변경 감지 (Dirty Checking)
JPA는 엔티티의 변경을 자동으로 감지한다.
Member member = em.find(Member.class, 1L);
member.setName("New Name"); // 값 변경
별도의 update 호출 없이도 트랜잭션 commit 시점에 JPA가 변경을 감지해서 UPDATE SQL을 생성한다.
동작 방식:
@DynamicUpdate
@org.hibernate.annotations.DynamicUpdate
이 옵션을 사용하면 변경된 컬럼만 포함한 UPDATE SQL이 생성된다. 불필요한 컬럼 업데이트를 줄이고 성능을 조금 더 최적화할 수 있다.
엔티티를 삭제하려면 먼저 삭제 대상 엔티티를 조회해야 한다.
Member memberA = em.find(Member.class, "memberA"); // 삭제 대상 조회
em.remove(memberA); // 엔티티 삭제
flush는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 과정이다.
em.flush();
tx.commit();
트랜잭션을 커밋하면 flush가 자동으로 실행된 후 commit이 진행된다. 따라서 대부분의 경우 flush를 직접 호출할 필요는 없다.
준영속 상태는 엔티티가 더 이상 영속성 컨텍스트에 의해 관리되지 않는 상태다. 이 상태에서는 엔티티를 수정해도 변경 사항이 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();